最近在做网络安全的 lab,需要用 Suricata 和 Snort3 对 PCAP 文件做流量分析。整个过程踩了不少坑,尤其是 Snort3 和 Snort2 的配置差异,以及 sticky buffer 这个概念。把完整的排查过程记录下来,方便以后回看。
Suricata:用 jq 过滤 EVE JSON 日志
Suricata 默认的 JSON 日志输出叫 EVE(Extensible Event Format),文件名通常是 eve.json。它把所有事件——alert、http、dns、tls、flow、ssh 等——都写进同一个 JSON 文件里,每行一个 JSON 对象。
这就带来了一个问题:文件里各种事件混在一起,直接看根本没法用。这时候 jq 就派上用场了。
过滤 HTTP 事件
目标很简单:从 /var/log/suricata/old_eve.json 里只提取 HTTP 事件。
jq 'select(.event_type=="http")' /var/log/suricata/old_eve.jsonselect() 是 jq 的过滤函数,只保留满足条件的 JSON 对象。输出结果:
{ "timestamp": "2023-07-06T08:34:35.859970+0000", "flow_id": 1252204100696793, "event_type": "http", "src_ip": "xxx.xxx.xxx.xxx", "dest_ip": "xxx.xxx.xxx.xxx", "dest_port": 80, "http": { "hostname": "adv.epostoday.uk", "url": "/app.php", "http_method": "GET", "status": 200, "length": 423 }}一共过滤出 3 条 HTTP 事件,全部属于同一个 flow(flow_id: 1252204100696793)。从源 IP 到目标 adv.epostoday.uk 的 80 端口,请求了 /app.php 和 /favicon.ico。
TIP
flow_id是 Suricata 给每个网络流分配的唯一标识。同一个 TCP 连接中的所有事件共享同一个 flow_id,用它可以快速关联同一会话的所有日志。
启用 HTTP 日志并分析 PCAP
Suricata 除了 EVE JSON,还有一个专门的 HTTP 日志模块——http-log。默认是关闭的,需要手动启用。
打开 /etc/suricata/suricata.yaml,找到 http-log 部分:
- http-log: enabled: no # 改成 yes filename: http.log append: yes把 enabled: no 改成 enabled: yes。用 sed 一行搞定:
sudo sed -i '/http-log:/{n;s/enabled: no/enabled: yes/}' /etc/suricata/suricata.yaml这个 sed 命令的意思是:找到包含 http-log: 的行,跳到下一行(n),然后把 enabled: no 替换成 enabled: yes。之所以要用这种写法,是因为 YAML 文件里可能有多个 enabled: no,直接全局替换会出问题。
然后用 Suricata 离线分析 PCAP 文件:
sudo suricata -r /home/htb-student/pcaps/suspicious.pcap-r 参数让 Suricata 读取 PCAP 文件而不是监听网卡。处理完之后,/var/log/suricata/http.log 就生成了:
12/24/2022-17:31:06.242839 adv.epostoday.uk[**]/app.php[**]Mozilla/5.0 ...12/24/2022-17:31:11.899970 adv.epostoday.uk[**]/app.php[**]Mozilla/5.0 ...12/24/2022-17:31:12.305197 adv.epostoday.uk[**]/favicon.ico[**]Mozilla/5.0 ...http.log 是行格式的日志,用 [**] 分隔字段。可以看到请求的 PHP 页面就是 app.php。
和 EVE JSON 相比,http.log 更轻量,适合快速浏览 HTTP 流量概况。但如果需要结构化查询或者和其他事件类型关联分析,还是 EVE JSON + jq 更灵活。
Snort3:规则、DAQ 和 PCAP 分析
接下来切换到 Snort3。和 Suricata 的 YAML 配置不同,Snort3 用 Lua 做配置语言,主配置文件叫 snort.lua。
Snort3 vs Snort2 的配置差异
这是我踩的第一个坑。直接跑 snort -c /etc/snort/snort.conf 报错找不到文件——因为 Snort3 根本不用 snort.conf。
Snort3 的配置体系:
snort.lua— 主配置文件,定义HOME_NET、模块参数、检测引擎选项等snort_defaults.lua— 默认参数,被snort.lua引用.rules文件 — 规则文件,语法和 Snort2 基本兼容但有扩展
在我操作的环境里,配置文件在 /root/snorty/etc/snort/snort.lua。里面 HOME_NET 默认设为 'any'。
运行 Snort 分析 PCAP
分析 wannamine.pcap,加载 local.rules 里的规则:
sudo snort -c /root/snorty/etc/snort/snort.lua \ --daq-dir /usr/local/lib/daq \ -r /home/htb-student/pcaps/wannamine.pcap \ -R /home/htb-student/local.rules \ -A cmg参数说明:
-c— 指定主配置文件--daq-dir— DAQ(Data Acquisition)模块路径,Snort3 通过 DAQ 抽象层读取数据源-r— 读取 PCAP 文件-R— 加载额外的规则文件(不在snort.lua中 include 的规则)-A cmg— 告警输出格式,cmg 会显示规则信息、包头和载荷
local.rules 里的第一条规则很简单:
alert icmp any any -> $HOME_NET any (msg:"ICMP test"; sid:1000001; rev:1; classtype:icmp-event;)这条规则会对所有进入 HOME_NET 的 ICMP 包触发告警。
运行结果:sid:1000001 触发了 234 次。wannamine.pcap 里有大量 ICMP 流量,这在挖矿相关的网络行为中不算意外——矿池通信和网络探测都可能产生 ICMP 包。
Log4Shell 规则与 Sticky Buffer
这是最有意思的部分。local.rules 里有一条被注释掉的 Log4Shell 检测规则:
#alert http any any -> any any (msg:"Log4shell Attempt Detected"; content:"|24 7b|jndi|3a|ldap|3a 2f 2f|",fast_pattern, nocase; sid:10000098; rev:1;)这条规则检测的是 Log4Shell(CVE-2021-44228)的经典 payload 模式:${jndi:ldap://。content 里用十六进制表示了特殊字符:|24 7b| 是 ${,|3a| 是 :,|2f 2f| 是 //。
问题来了:去掉注释直接跑,对着 log4shell.pcap 一个告警都不触发。为什么?
因为这条规则缺少 sticky buffer 声明。
什么是 Sticky Buffer
在 Snort3 中,sticky buffer 是一个关键概念。它告诉检测引擎应该在数据包的哪个部分去匹配 content。
没有 sticky buffer 时,content 默认匹配原始 payload。但对于 HTTP 协议,Snort3 的 http_inspect 模块会把请求拆解成各个字段——URI、header、body 等。如果你想匹配特定字段,必须用对应的 sticky buffer。
常用的 HTTP sticky buffer:
| Keyword | 匹配范围 |
|---|---|
http_uri | HTTP URI |
http_header | HTTP 头部(包括 User-Agent、Host 等) |
http_client_body | HTTP 请求体 |
http_method | HTTP 方法(GET、POST 等) |
NOTESticky buffer 这个名字的意思是:一旦声明了某个 buffer,后续所有 content 匹配都”粘”在这个 buffer 上,直到声明下一个 buffer。这是 Snort3 替代 Snort2 PCRE 修饰符(B, U, P, H 等)的新机制。
修复规则
题目说 payload 在 User-Agent 里。User-Agent 是 HTTP header 的一部分,所以需要在 content 前加上 http_header:
alert http any any -> any any (msg:"Log4shell Attempt Detected"; http_header; content:"|24 7b|jndi|3a|ldap|3a 2f 2f|",fast_pattern, nocase; sid:10000098; rev:1;)加上 http_header; 之后重新跑:
sudo snort -c /root/snorty/etc/snort/snort.lua \ --daq-dir /usr/local/lib/daq \ -r /home/htb-student/pcaps/log4shell.pcap \ -R /home/htb-student/local.rules \ -A cmg这次触发了 15 条告警。攻击者在 HTTP 请求的 User-Agent 字段里嵌入了 ${jndi:ldap://...} payload,试图触发目标服务器上 Log4j 的 JNDI 查找,从而实现远程代码执行。
WARNING实际环境中 Log4Shell 的 payload 会有各种混淆变体,比如
${${lower:j}ndi:ldap://...}或嵌套${env:...}。单纯匹配明文 pattern 不够,生产环境需要更复杂的规则集。
总结
这次 lab 覆盖了两个主流开源 IDS 的核心工作流:
Suricata 部分:
jq select()过滤 EVE JSON 是最常用的分析手段http-log模块默认关闭,需要手动启用-r参数做离线 PCAP 分析
Snort3 部分:
- 配置从
snort.conf迁移到了snort.lua(Lua 语言) - DAQ 抽象层需要用
--daq-dir指定模块路径 - Sticky buffer 是 Snort3 规则编写的核心概念——不指定 buffer,HTTP 层的 content 匹配就不会命中
http_header覆盖所有 HTTP 头部字段,包括 User-Agent
最大的收获是理解了 sticky buffer。Snort2 时代用 PCRE 修饰符来指定匹配位置,语法晦涩。Snort3 改用 sticky buffer 后语义更清晰,但也意味着写规则时必须显式声明匹配范围,否则规则就是哑的。