能编写检测恶意 URI、请求头与请求体的 HTTP 规则,并用授权 PCAP 回放验证。
上一节我们把 HTTP 归一化缓冲区走了一遍:请求被解析成方法、URI、请求头、状态码等字段,先解码再匹配,SQL 注入、可疑 User-Agent、500 响应都落到了对应缓冲区上,也排查过规则没命中的常见原因。这一节接着动手,写三条 HTTP 规则:一条盯住恶意 URI,一条盯住恶意 User-Agent,一条盯住恶意请求体。目标很具体——写完规则,用授权 PCAP 回放,fast.log 里要看到告警,正常 Web 流量不能报。
三个攻击面正好对应三个缓冲区:目录穿越与注入特征出现在请求行 URI 里,扫描器身份写在 User-Agent 里,命令注入载荷藏在 POST 请求体里。规则就是一条指令:告诉 Suricata 找什么流量、找到后做什么。
这是动手课:每写完一条规则就回放验证,不要一口气写完三条再查。流程沿用上一节:先在 Wireshark 里看清 PCAP 的请求长什么样,再写规则,最后用 -r 回放核对 fast.log。先看目录穿越。
核心概念
在 URI 上匹配目录穿越与注入特征
类比:门卫查验访客登记簿的“到访路径”一栏,不看落款、不看备注,只盯路径本身。目录穿越与注入特征都藏在路径里,所以检测员只要盯住这一段。
定义:http_uri 是缓冲区修饰符,让 content 与 pcre 只在归一化后的 HTTP URI 里搜索。目录穿越表现为 .. 点段序列把路径引向系统目录,如 ../../etc/passwd;注入特征表现为 cmd=、exec= 之类参数后接命令。归一化会改写部分字节,因此规则行为最终要以真实 PCAP 验证为准。
匹配策略:单个 content 命中面太宽,.. 会出现在正常路径里。把 content 与 pcre 组合在同一缓冲区上:content 先做廉价定位,pcre 再校验结构,兼顾速度与精度。
目录穿越规则写成这样:
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Suspicious HTTP directory traversal in URI"; flow:to_server,established; content:".."; http_uri; nocase; pcre:"/\.\.\/[a-z0-9_\/]+\.(php|asp|aspx|jsp)/i"; http_uri; sid:1000001; rev:1;)$HOME_NET 与 $EXTERNAL_NET 是配置里的地址变量,默认把 RFC 1918 地址段当作内网,其余视为外部。flow:to_server,established 只看已建立连接中发往服务端的流量,减少对握手包的无效计算。content:".."; http_uri; 把 .. 的搜索锁在 URI 里;pcre 再要求点段之后出现脚本扩展名,把版本号、扩展名里的双点误报压下去。content 的大小写由 nocase 控制,pcre 内部的大小写由 /i 控制,两者都要写,缺一个就漏掉大小写变体。
方向按你的部署调:内网主机向外发起恶意请求用 $HOME_NET -> $EXTERNAL_NET;外部攻击内网 Web 服务就反过来。回放时若这条规则没命中,先回 Wireshark 看请求行原始字节:编码后的穿越(如 %2e%2e)与归一化结果可能与你写的不一样。规则以实际字节为准,这是离线验证存在的意义。元数据关键字按上节习惯补齐。
核心概念
在请求头里识别扫描器身份
类比:安检台检查信封的“寄件人”栏。恶意工具常在 User-Agent 里写明自己的身份,像寄件人把名字写在信封上——SQLMap 会带 sqlmap,Nikto 会带 Nikto。
定义:http_header 是缓冲区修饰符,让 content 只在 HTTP 请求头或响应头里搜索。匹配工具名时加 nocase,因为头部值的大小写不固定,服务端也常改写。
匹配策略:User-Agent 是最容易伪造的字段,单凭它告警会吵,通常再叠加一个请求特征,比如固定 URI 或请求方法。写规则时先宽松后收紧:先让它在授权 PCAP 上命中,再逐条加条件压误报。
一条基于 User-Agent 的规则:
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Suspicious SQLMap User-Agent"; flow:to_server,established; http_header; content:"sqlmap"; nocase; sid:1000002; rev:1;)http_header; content:"sqlmap"; 表示只在头部里找 sqlmap。这个写法命中所有头部;要精确锁定 User-Agent 一行,用 pcre 从行首锚定:pcre:"/User-Agent:[^\r\n]*sqlmap/i"; http_header;。先用宽松版跑通,误报再收紧。
这一条先不加 pcre,靠 http_header 限定范围、nocase 容忍大小写。若正常流量里也有 sqlmap 字样(比如站点本身被扫描过),再在 pcre 里锚定行首,或追加第二个 content 验证请求路径。规则迭代的方向永远是:先命中,再收敛。sid、rev、classtype、reference 按上节习惯补齐。
核心概念
在请求体里串出注入命令
类比:信封查完,开箱检查内容物。真正的注入载荷往往藏在 POST 请求体里,不在信封表面。
定义:http_client_body 是缓冲区修饰符,让 content 只在请求体里搜索。请求体常是 URL 编码或分段的表单内容,单个关键字可能被拆开,因此把多个 content 用 distance 与 within 串起来。
匹配策略:distance 与 within 上节已学过,这里把它们用在请求体上:第一个 content 定位起点,后面的 content 用 distance 表示从前一个匹配结束处开始算,用 within 限定最大跨度。它们表达“这些字节紧挨着出现”的语义,而不是散落在请求体任何位置。
检测请求体里的 netcat 反弹 shell:
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"Suspicious netcat command in HTTP request body"; flow:to_server,established; http_client_body; content:"nc"; nocase; content:"/bin/sh"; nocase; distance:0; within:100; sid:1000003; rev:1;)http_client_body 让两个 content 都在请求体里找。content:"nc" 先命中;distance:0 让第二个 content 从前一个匹配的结束位置开始找;within:100 要求 /bin/sh 出现在 100 字节以内。这正是授权 PCAP 里 POST 注入载荷的典型形状:nc 反弹到攻击者地址并拉起 shell。
如果请求体被 URL 编码,/bin/sh 可能变成 %2fbin%2fsh,nc 与 shell 之间的距离也会变。此时先看 Wireshark 里的解码视图,再决定改 pcre 还是保留双 content 方案。
回放顺序固定为:恶意 PCAP 先跑,确认命中;正常 PCAP 再跑,确认安静。顺序反过来,容易把上一次的残留告警算成误报。
只验证恶意 PCAP 命中还不够。规则要在真实网络里活下来,必须证明它对正常 Web 流量沉默。
准备一份干净的正常 Web 流量 PCAP(授权抓取),与恶意 PCAP 用同一个 -r 命令回放,fast.log 不应新增这三条规则的告警。若出现误报,回到 Wireshark 比对两种流量的字节差异,收紧 content 或 pcre。这一步把“能告警”推进到“低误报”。
三条规则只在本机授权 PCAP 与靶场上回放,不得对未授权目标发起探测。课程排除项明确:不对真实生产目标做攻击性测试,不含规避检测与反取证教学。Wireshark 只用来分析你自己捕获或授权取得的流量,PCAP 的来源要能交代清楚。
eve.json 每行一条 JSON 事件,肉眼难读,官方快速入门就建议顺手安装 jq。回放后过滤告警事件,例如按 event_type 与 alert.signature_id 查看三条规则。先看 event_type 是否为 alert,再看 alert.signature_id 是否等于规则 sid;两者对上才算真正命中。
写规则并回放验证
- 1
用 Wireshark 打开授权恶意 PCAP,确认三处特征:请求行 URI 里的目录穿越、User-Agent 里的扫描器标识、POST 请求体里的注入命令。只分析授权取得的流量。
- 2
在规则目录新建 local.rules。
suricata-update默认把规则安装到 /var/lib/suricata/rules;你的实际路径以 suricata.yaml 为准,路径错误时从启动日志逐条核对。bashsudo vim /var/lib/suricata/rules/local.rules - 3
把三条规则写入 local.rules,sid 分别用 1000001、1000002、1000003,避免与现有规则重复。msg 写清告警含义,方便日后检索。
- 4
用上节学过的测试模式(-T)校验语法,规则全部通过再继续。报错时先看提示的关键字拼写。
- 5
回放恶意 PCAP:
bashsuricata -r /path/to/malicious.pcap - 6
查看日志目录下的 fast.log,确认三条规则的告警都出现。没有输出时,检查 suricata.yaml 是否启用了 fast.log 输出,再确认规则确实被加载。
bashtail -f /var/log/suricata/fast.log - 7
回放正常 Web 流量的 PCAP,确认这三条规则没有新增告警;若有误报,回到 Wireshark 比对字节并收紧规则。
- 8
用 eve.json 核对告警的完整字段,确认告警归属正确。可用 jq 过滤,快速定位 alert.signature_id 与 http 相关字段。
你应该看到
回放恶意 PCAP 后,fast.log 出现 sid 1000001、1000002、1000003 三条告警;回放正常 Web 流量时这三条规则无告警。eve.json 中能看到对应的告警事件与 HTTP 字段。
遇到问题:回放恶意 PCAP 后 fast.log 里没有出现预期告警,或正常流量回放时误报。
可能原因
- suricata.yaml 里没有启用 fast.log 输出。
- 规则文件路径与 suricata.yaml 配置不一致,规则根本没被加载。
- 特征与实际字节不符:大小写不同、URL 编码、归一化改写了字节。
- 缓冲区用错或方向写反,例如特征在请求体里,却只在 http_uri 上匹配。
- pcre 写得太严或转义有误:测试模式只查语法,不保证运行时一定命中。
排查步骤
- 在 suricata.yaml 里启用 fast.log,重新回放。
- 查看启动日志,把每条路径与配置逐项核对。
- 回到 Wireshark 看原始请求字节,逐字节比对;大小写不确定就加 nocase。
- 把三条规则拆开单独回放,定位是哪一条没命中;用 eve.json 看实际命中的内容。
- 误报时给规则追加第二个 content 或收紧 pcre,而不是删掉规则;反复回放直到恶意与正常流量行为都符合预期。
自检:为什么目录穿越规则要把 content 与 pcre 组合在 http_uri 上,而不是只写一个 content:".."?
http_uri 把搜索范围限定在归一化后的 URI;但单个 content:".." 会命中所有含双点号的正常路径,比如版本号或扩展名,误报很高。pcre 进一步要求出现点段序列并指向脚本扩展名,用更具体的结构压低误报——这正是上节 content、nocase、pcre 组合的复用。若 pcre 写太严导致漏报,先回 Wireshark 看真实字节,再放宽结构条件。