PyShark 是一个Python库,它提供了一个方便的接口来使用Wireshark的命令行工具tshark进行包解析。它是基于Wireshark开发的,,你可以直接在Python中利用Wireshark强大的解析器处理捕获文件或实时数据流。但是网上对使用pyshark来分析http流量的说明文章太少,于是手动记录下。
pyshark的基础使用 1. 安装 1‑1. 先确保你已安装 Wireshark / tshark 1 2 3 4 5 6 7 8 sudo apt-get install wireshark tshark brew install wireshark
注意 :在 Linux 上,你可能需要把当前用户加入 wireshark 组或使用 sudo 执行 tshark。
1‑2. 安装 PyShark
如果你想要支持更高版本的 tshark,建议先更新 Wireshark,然后再安装/升级 pyshark。
2. 基本概念
对象
含义
FileCapture
从已有 .pcap/.pcapng 文件读取包
LiveCapture
在指定网络接口上实时捕获包
Packet
单个协议帧/数据包/数据报,提供访问字段的方法
Layer
协议层(Ethernet、IP、TCP 等),可通过属性或字典方式访问
3.基本使用 FileCapture与LiveCapture的读取方法1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import pysharkfrom pathlib import Pathpcap_path = Path(r"att.pcapng" ) if not pcap_path.is_file(): raise FileNotFoundError(f"File {pcap_path} does not exist." ) capture = pyshark.FileCapture( str(pcap_path), keep_packets=False , display_filter='ip' , ) for pkt in capture: try : src_ip = pkt.ip.src dst_ip = pkt.ip.dst protocol = pkt.transport_layer or 'Unknown' print(f"Packet: {protocol} from {src_ip} to {dst_ip} " ) except AttributeError as e: continue capture.close()
代码说明,通过pyshark.FileCapture和pyshark.LiveCapture 获得的 capture 的变量是一个FileCapture对象,我们可以里理解为被pyshark的流量包文件对象。而代码中pkt 变量是一个Packet 对象可以理解为流量包文件每一个的每一个流量包。而流量包的读取顺序则是和 Wireshark中No.的顺序是相似的(在通过 display_filter过滤后流量包的读取顺序再用索引下标读取的话pyshark会对流量包重新排序)。
而且pyshark.FileCapture和pyshark.LiveCapture 通常情况下我们只需要input_file指定文件或interface指定对应网卡,就可以分析到流量包。但为了分析方便开发者加了扩展参数来自进行分析,我对pyshark.FileCapture和pyshark.LiveCapture 的常见的参数/扩展参数总结如下:
参数
作用
默认值
使用建议 / 注意事项
keep_packets
是否在读取后保留已读的包对象。 True → 内存中保存所有 Packet;False → 只返回一个包,随后被 GC 回收。
True
大文件(GB+)时建议设为 False,以防内存爆炸。
input_file
指定要读取的捕获文件或 XML 可直接传入路径字符串、Path 对象或已打开的文件句柄(io.BufferedReader)。
None (必须提供)
只在 FileCapture 中使用;若为文件句柄,tshark 会从当前位置开始读。
ring_file_size
循环文件大小(kB) 当使用 output_file 或开启循环写入时,tshark 会把抓到的包写成一系列 “环形” 文件。
1024 kB (1 MiB)
对高流量捕获建议增大;过小会导致文件频繁切换、磁盘 I/O 增加。
num_ring_files
循环文件数量 保留的文件数,tshark 会在此数目后循环覆盖旧文件。
1
典型场景:想把最近 5 MiB 数据保存下来,只需 num_ring_files=5。
ring_file_name
循环文件基础名 实际写出的文件会以 <name>.0, <name>.1, … 命名。
/tmp/pyshark.pcap
在 Windows 建议改为 C:\\temp\\pyshark.pcap,避免路径中的斜杠被误解析。
interface
抓包的网卡名称 如果不指定,tshark 会自动选第一个可用接口。
第一个可用接口
对多网卡机器一定要明确写 interface='eth0' 或者 en0(macOS)。
bpf_filter
BPF 过滤器(Berkeley Packet Filter) 在捕获层面就筛选掉不需要的包,速度快。
None
示例:bpf_filter='tcp port 80';注意:BPF 与 Wireshark 的显示过滤不同。
display_filter
Wireshark 显示过滤(tshark 层面的 -Y) 在解析后再筛选,功能更强大但开销较大。
None
示例:display_filter='http.request';如果你只需要 IP/TCP/UDP 信息,可把它留空或使用 BPF。
only_summaries
仅生成包摘要 不解析完整协议层,速度快且内存占用低。
False
对日志收集、流量计数非常合适;但无法获取字段值,只能拿到 pkt.summary。
disable_protocol
禁用某个协议的检测(tshark > 2) 可加速解析,尤其是你知道不需要某些大协议时。
None
用法:disable_protocol='ssl';如果禁用了错误协议,后续访问会报错。
decryption_key
对加密流量进行解密 用于 Wi-Fi 或 VPN 等加密捕获。
None
需要对应 encryption_type;若未正确设置,tshark 会忽略此字段。
encryption_type
指定加密标准 必须是 'WEP', 'WPA-PWD', 或 'WPA-PWK'(默认 WPA-PWK)。
'WPA-PWK'
与 decryption_key 配合使用;若不匹配,解密失败。
tshark_path
tshark 可执行文件的完整路径 如果系统 PATH 中没有 tshark 或你想指定特定版本。
自动搜索
在 Windows 上常用:r'C:\Program Files\Wireshark\tshark.exe'。
output_file
把抓到的数据写入文件(可与 ring_file_* 配合) 即使你不想保存,也可以把它设为 None 或省略。
None
若同时指定了 ring_file_name,tshark 会将数据写成环形;否则直接写一个普通 pcap 文件。
Packet 对象再说回Packet 对象,一个Packet本质上就对应一个流量包,在pyshark中提供了如下基础方法:
layers : 获取数据包Packe所有的协议
get_multiple_layers(layer_name) : 获取数据包中指定名称的多个层(例如,一个数据包中可能有多个IP层)
get_raw_packet() : 获取原始的字节数据包
highest_layer : 返回数据包的最高协议层(例如,应用层协议名称)
pretty_print() : 以美观的格式打印数据包详情
show() : 类似于 pretty_print(),用于显示数据包详情
sniff_time : 数据包被捕获的时间戳
transport_layer : 返回传输层协议(如TCP或UDP)
Layer 对象这样我们从Packet 对象获得到Layer 对象,Layer 对象对应会根据协议的不同,给我们提供不同的方法。比如TCP Layer 协议对象会给我们提供:
srcport : 源端口号
dstport : 目标端口号
port : 可能是源端口或目标端口(取决于上下文)
seq : 序列号(可能经过相对处理)
seq_raw : 原始序列号
ack : 确认号(可能经过相对处理)
ack_raw : 原始确认号
nxtseq : 下一个序列号
stream : TCP流索引(等同于Wireshark中的tcp.stream)
checksum : 校验和
checksum_status : 校验和状态
len : TCP段长度
hdr_len : TCP头部长度
time_relative : 相对于第一个数据包的时间
time_delta : 与前一个数据包的时间差
analysis : 分析信息
completeness : 连接完整性状态
而IP Layer 协议会给我们提供ipv4 相关网络成相关的协议的方法(ps:如果是ipv6 则是IPV6 Layer ,在pyshark中这里有区分的):
src : 源IP地址
dst : 目标IP地址
addr : 可能是源或目标IP地址(取决于上下文)
dst_host : 目标主机地址
version : IP版本(通常是4或6)
hdr_len : IP头部长度
dsfield : 区分服务字段(DS Field)
dsfield_dscp : 差分服务代码点(DSCP)
dsfield_ecn : 显式拥塞通知(ECN)
len : IP数据包总长度
id : 标识字段(用于分片和重组)
flags : 标志位
pyshark分析http流量 HTTP基础信息读取 同样pyshark的提供了HTTP Layer的方法来对http流量进行分析。常见的方法如下:
用途
属性方法
结果示例
请求方法
request_method
GET, POST
完整 URI
request_full_uri
https://example.com/api?x=1
URI 片段
request_uri
/api?x=1
HTTP 版本
request_version / response_version
HTTP/1.1
状态码
response_code
200
响应文本
response_phrase
OK
内容类型
content_type
application/json
Cookie
cookie_pair / set_cookie
sessionid=abc123; ...
在抓取 HTTP 流量时,需要注意 请求包 与 响应包 的区别。PyShark 在读取 HTTP 包时,只是粗略地保留了一些属性,并没有为它们做出专门的区分。因此,如果你在遍历包时对一个请求包使用了仅存在于响应包中的属性(例如 response_code、response_phrase),就会导致报错。
解决办法很简单:
请求包 具有 request_method 属性;
响应包 具有 response_code 属性。
先根据这两个标识判断包的类型,再使用对应的属性进行处理,就能避免错误。
另一个常见需求是提取 HTTP 包中的主体数据:
对于请求包,获取的是请求体(body);
对于响应包,获取的是返回体(body)。
在处理时同样需要先区分包类型,然后再调用相应的属性或方法来读取主体内容。
常见的方法是对读取file_data 来进行处理,但是file_data默认是十六进制的数据需要对数据进行处理。但file_data 默认是16进制字符,对于pkt[‘DATA-TEXT-LINES’]的layer来获得body内容,而且代码只需要如下小部分,但是pkt[‘DATA-TEXT-LINES’]一班只在返回包里存在:
1 2 3 if hasattr(pkt, 'DATA-TEXT-LINES' ): body = str(pkt['DATA-TEXT-LINES' ]) print('data:\n' +(body))
最后,为了方便理解我写了一个按照顺序遍历http流量的样例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import pysharkimport urllib.parse, binasciiPCAP_FILE = r"C:/Users/LEXS/Desktop/att.pcapng" caps = pyshark.FileCapture(PCAP_FILE,display_filter='http' ) for pkt in caps: if hasattr(pkt.http, 'request_method' ): print("\n--- HTTP REQUEST ---" ) print(f"Method : {pkt.http.request_method} " ) print(f"URI : {pkt.http.request_uri} " ) print(f"Version: {pkt.http.request_version} " ) print(f"Host : {getattr(pkt.http, 'host' , '<none>' )} " ) print(f"User‑Agent: {getattr(pkt.http, 'user_agent' , '' )} " ) print(f"cookie: {getattr(pkt.http, 'cookie' , '' )} " ) if hasattr(pkt.http, 'file_data' ): body = urllib.parse.unquote_plus(binascii.unhexlify(pkt.http.file_data.replace(":" , "" )).decode('utf-8' )) print('data:\n' +(body)) elif hasattr(pkt.http, 'response_code' ): print("\n--- HTTP RESPONSE ---" ) print(f"Status : {pkt.http.response_code} {pkt.http.response_phrase} " ) print(f"Version: {pkt.http.response_version} " ) print(f"Server : {getattr(pkt.http, 'server' , '<none>' )} " ) if hasattr(pkt.http, 'file_data' ): body = urllib.parse.unquote_plus(binascii.unhexlify(pkt.http.file_data.replace(":" , "" )).decode('utf-8' )) print('data:\n' +(body))
HTTP请求包与响应包匹配问题 细心的同学可以发现在上面按照顺序遍历的http流量时会出现一个问题,我们在区分请求包与响应包是通过request_method和response_code两个特征在进行遍历流量包的。在一般情况下,我们发出的去的一个流量包应该对应一个返回包,会想下面这样一请求一回复:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 --- HTTP REQUEST --- Method : GET URI : /store/ Version: HTTP/1.1 Host : 123.56.176.94 User‑Agent: sqlmap/1.8.12#stable (https://sqlmap.org) cookie: id=1%27+AND+ORD%28MID%28%28SELECT+IFNULL%28CAST%28password+AS+NCHAR%29%2C0x20%29+FROM+store.users+ORDER+BY+%60user%60+LIMIT+0%2C1%29%2C14%2C1%29%29%3E54+AND+%27wfQx%27%3D%27wfQx; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42 --- HTTP RESPONSE --- Status : 404 Not Found Version: HTTP/1.1 Server : nginx/1.27.4 data:......... --- HTTP REQUEST --- Method : POST URI : /store/cookie-input.php Version: HTTP/1.1 Host : 123.56.176.94 User‑Agent: sqlmap/1.8.12#stable (https://sqlmap.org) cookie: id=1%27+AND+ORD%28MID%28%28SELECT+IFNULL%28CAST%28password+AS+NCHAR%29%2C0x20%29+FROM+store.users+ORDER+BY+%60user%60+LIMIT+0%2C1%29%2C14%2C1%29%29%3E54+AND+%27wfQx%27%3D%27wfQx; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42 data: id=1' AND ORD(MID((SELECT IFNULL(CAST(password AS NCHAR),0x20) FROM store.users ORDER BY `user` LIMIT 0,1),14,1))>51 AND 'wfQx'='wfQx&Submit=Submit --- HTTP RESPONSE --- Status : 200 OK Version: HTTP/1.1 Server : nginx/1.27.4 data:........
但是也会发生下面这种http只有请求包没有返回包的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 --- HTTP REQUEST --- Method : POST URI : /store/cookie-input.php Version: HTTP/1.1 Host : 123.56.176.94 User‑Agent: sqlmap/1.8.12#stable (https://sqlmap.org) cookie: id=1%27+AND+ORD%28MID%28%28SELECT+IFNULL%28CAST%28password+AS+NCHAR%29%2C0x20%29+FROM+store.users+ORDER+BY+%60user%60+LIMIT+3%2C1%29%2C8%2C1%29%29%3E51+AND+%27WZKg%27%3D%27WZKg; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42 ip.src: 192.168.1.4 ip.dst: 123.56.176.94 data: id=1' AND ORD(MID((SELECT IFNULL(CAST(password AS NCHAR),0x20) FROM store.users ORDER BY `user` LIMIT 3,1),8,1))>52 AND 'WZKg'='WZKg&Submit=Submit --- HTTP REQUEST --- Method : GET URI : /store/ Version: HTTP/1.1 Host : 123.56.176.94 User‑Agent: sqlmap/1.8.12#stable (https://sqlmap.org) cookie: id=1%27+AND+ORD%28MID%28%28SELECT+IFNULL%28CAST%28password+AS+NCHAR%29%2C0x20%29+FROM+store.users+ORDER+BY+%60user%60+LIMIT+3%2C1%29%2C8%2C1%29%29%3E52+AND+%27WZKg%27%3D%27WZKg; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42 ip.src: 192.168.1.4 ip.dst: 123.56.176.94 --- HTTP REQUEST --- Method : POST URI : /store/cookie-input.php Version: HTTP/1.1 Host : 123.56.176.94 User‑Agent: sqlmap/1.8.12#stable (https://sqlmap.org) cookie: id=1%27+AND+ORD%28MID%28%28SELECT+IFNULL%28CAST%28password+AS+NCHAR%29%2C0x20%29+FROM+store.users+ORDER+BY+%60user%60+LIMIT+3%2C1%29%2C8%2C1%29%29%3E52+AND+%27WZKg%27%3D%27WZKg; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42 ip.src: 192.168.1.4 ip.dst: 123.56.176.94 data: id=1' AND ORD(MID((SELECT IFNULL(CAST(password AS NCHAR),0x20) FROM store.users ORDER BY `user` LIMIT 3,1),9,1))>47 AND 'WZKg'='WZKg&Submit=Submit --- HTTP RESPONSE --- Status : 200 OK Version: HTTP/1.1 Server : nginx/1.27.4 ip.src: 123.56.176.94 ip.dst: 192.168.1.4 data:
可以看到极端情况下,http的请求包在发出后不一定得到匹配的回复。这时如何如果我们盲目进行根据request_method和response_code两个特征和遍历顺序匹配,就会导致访问/store/cookie-input.php的请求包却匹配到/store/的响应包,从而得到一个错误结果。但是不要忘了,http 大部分情况下,是基于TCP协议实现的。而在TCP layer 中提供了stream 方法,即 TCP流索引(等同于Wireshark中的tcp.stream)我们可以遍历流量包将所有我们需要的HTTP流量的请求包的tcp.stream记录,再找出对应相关tcp.stream的响应包,即可以得到一个HTTP请求包与响应包。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import pysharkimport urllib.parse, binasciiPCAP_FILE = r"att.pcapng" caps = pyshark.FileCapture(PCAP_FILE,display_filter='http' ) http_pkt={} next_response=None for pkt in caps: if hasattr(pkt.http, 'request_method' ) : if pkt.http.request_uri == '/store/' : cookie=urllib.parse.unquote_plus(getattr(pkt.http, 'cookie' , '' )) http_pkt[pkt.tcp.stream]={'request_pkt' :None ,'response_pkt' :None } http_pkt[pkt.tcp.stream]['request_pkt' ]=pkt next_response=pkt.tcp.stream elif hasattr(pkt.http, 'response_code' ) and next_response==pkt.tcp.stream: http_pkt[pkt.tcp.stream]['response_pkt' ]=pkt print(http_pkt)
这样我们就能从流量中,只匹配出访问了/store/url 的请求包与对应的响应包。然后用AI润色下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 import pysharkimport urllib.parsefrom pathlib import PathPCAP_FILE = r"att.pcapng" def load_packets (pcap_path) : """把所有 HTTP 包一次性读进内存(按需可改成迭代)""" with pyshark.FileCapture( pcap_path, display_filter="http" , keep_packets=False ) as caps: return list(caps) def main () : packets = load_packets(PCAP_FILE) http_pairs = {} for pkt in packets: if not hasattr(pkt.http, 'layer_name' ): continue stream_id = pkt.tcp.stream if hasattr(pkt.http, 'request_method' and pkt.http.request_uri == '/store/) : #只记录访问/store/的流量包 cookie_raw = getattr(pkt.http, ' cookie', ' ') cookie = urllib.parse.unquote_plus(cookie_raw) print(f"[{stream_id}] request cookie: {cookie}") http_pairs.setdefault(stream_id, {})[' request'] = pkt continue # ---------- 响应 ---------- if hasattr(pkt.http, ' response_code'): if stream_id in http_pairs : if ' request' not in http_pairs[stream_id]: print(f"[{stream_id}] 发现响应但没有对应请求,跳过") continue else : #只匹配到记录的请求包的对应响应包 http_pairs.setdefault(stream_id, {})[' response'] = pkt # ---------- 输出 ---------- for stream, pair in http_pairs.items(): req = pair.get(' request') res = pair.get(' response') print("\n=== Stream:", stream, "===") if req: print(f" Request: {req.http.request_method} {req.http.request_uri}") else: print(" <no request captured>") if res: print(f" Response: {res.http.response_code} {res.http.response_phrase}") else: print(" <no response captured>") if __name__ == "__main__": main()
样例分析 之前遇到一个有意思的流量分析题目,其中一个考虑是分析流量包里面二次布尔注入考点。题目用POST 提交payload来登录用户,payload会转移到cookie中。
1 2 3 4 5 6 7 8 9 10 11 12 POST /store/cookie-input.php HTTP/1.1Host : 123.56.176.94User-Agent : sqlmap/1.8.12#stable (https://sqlmap.org)Connection : closeContent-Length : 246Accept : */*Accept-Encoding : gzip,deflateCache-Control : no-cacheContent-Type : application/x-www-form-urlencoded; charset=utf-8Cookie : id=1; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42id=1%27%20AND%20ORD%28MID%28%28SELECT%20IFNULL%28CAST%28table_name%20AS%20NCHAR%29%2C0x20%29%20FROM%20INFORMATION_SCHEMA.TABLES%20WHERE%20table_schema%3D0x73746f7265%20LIMIT%200%2C1%29%2C6%2C1%29%29%3E64%20AND%20%27SpqU%27%3D%27SpqU&Submit=Submit
登录后会自动GET 访问/store/
1 2 3 4 5 6 7 8 GET /store/ HTTP/1.1Host : 123.56.176.94User-Agent : sqlmap/1.8.12#stable (https://sqlmap.org)Connection : closeAccept : */*Accept-Encoding : gzip,deflateCache-Control : no-cacheCookie : id=1%27+AND+ORD%28MID%28%28SELECT+IFNULL%28CAST%28password+AS+NCHAR%29%2C0x20%29+FROM+store.users+ORDER+BY+%60user%60+LIMIT+3%2C1%29%2C6%2C1%29%29%3E87+AND+%27WZKg%27%3D%27WZKg; security=high; PHPSESSID=tlse8ootcss5qp5c41n0spjf42
若cookie里面payload执行为Fales ,响应包里会带有User ID is MISSING from the database.,若执行为Ture响应包里会带有User ID exists in the database. 同时不仅cookie里payload用二分法来进行判断,而且部分流量请求包,没有对于响应包。因此正好需要先获得http请求包和响应包,再通过判断请求包里的cookie和响应包里回显有User ID is MISSING from the database.还是有User ID exists in the database.来进行判断。加之没有响应包提示,我们要将之没有响应包的payload提示出来供我们自行判断。综上我们可以形成下面exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import pysharkimport urllib.parse, binasciiPCAP_FILE = r"att.pcapng" caps = pyshark.FileCapture(PCAP_FILE,display_filter='http' ) http_pkt={} next_response=None for pkt in caps: if hasattr(pkt.http, 'request_method' ) : if pkt.http.request_uri == '/store/' : cookie=urllib.parse.unquote_plus(getattr(pkt.http, 'cookie' , '' )) http_pkt[pkt.tcp.stream]={'request_pkt' :None ,'response_pkt' :None } http_pkt[pkt.tcp.stream]['request_pkt' ]=pkt next_response=pkt.tcp.stream elif hasattr(pkt.http, 'response_code' ) and next_response==pkt.tcp.stream: http_pkt[pkt.tcp.stream]['response_pkt' ]=pkt flag='' for k in range(1 ,20 ): True_num=None pay="id=1' AND ORD(MID((SELECT IFNULL(CAST(password AS NCHAR),0x20) FROM store.users ORDER BY `user` LIMIT 2,1),{}," .format(k) for i in http_pkt: cookie=urllib.parse.unquote_plus(http_pkt[i]['request_pkt' ].http.cookie) if pay in cookie: if http_pkt[i]['response_pkt' ]!= None : print(cookie + str('exists' in urllib.parse.unquote_plus(binascii.unhexlify(http_pkt[i]['response_pkt' ].http.file_data.replace(":" , "" )).decode('utf-8' )))+'-' +str(i)) if 'MISS' in urllib.parse.unquote_plus(binascii.unhexlify(http_pkt[i]['response_pkt' ].http.file_data.replace(":" , "" )).decode('utf-8' )) : True_num=chr(int(cookie.split('>' )[1 ].split(' AND' )[0 ])) else : print('无返回包:' +cookie+'--' +str(int(cookie.split('>' )[1 ].split(' AND' )[0 ]))+'--' +str(i)) flag=flag+True_num print(True_num) print(flag)