能使用 content 关键字匹配 ASCII 与二进制载荷,并用转义语法表达不可见字符。
上一课你已经用离线回放产出告警:先读 fast.log 确认有没有告警,再用 jq 从 eve.json 中按 sid 筛选 alert 事件,逐字段核对 msg、classtype、reference 与规则定义是否一致,掌握了用结构化输出验证规则的方法。但到目前为止,规则只规定了“看谁”:源地址、目的地址、端口与方向。这些条件能缩小检查范围,却说明不了流量为什么可疑。真正让规则具备检测能力的是规则体里的匹配选项。第一个要掌握的载荷匹配关键字,就是 content。
核心概念
content:在载荷中查找字面量
类比:把 Suricata 想象成快递安检员。安检员先核对地址标签,标签对上了才打开包裹。打开后,他按清单逐项翻找:清单写“找一罐 X”,他就只找一罐 X,不会去认“看起来有点像 X”的罐子。规则头就是地址标签,content 就是清单条目。 定义:content 是规则体中的载荷匹配关键字,指示 Suricata 在报文载荷(payload)中查找一段字面量。字面量可以是 ASCII 文本,也可以是一串十六进制字节。查找是精确的逐字节匹配,content 不做正则解释,也不会模糊匹配。匹配默认区分大小写:“GET”不会命中“get”。一条规则可以写多个 content,规则要求每个条件都在载荷中出现,全部命中才成立。content 直接面对报文载荷里的字节,查找不按词语边界切分:载荷里任意位置包含这段字节序列就算命中。
匹配 ASCII 文本最直观:把要查找的字符串放进双引号。Suricata 会逐字节在载荷中搜索这个字面量。
alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"检测到明文 GET 请求"; content:"GET"; sid:1002001; rev:1;)这条规则要求载荷中出现大写 GET 三个连续字节。默认大小写敏感,所以 get、Get、gEt 都不会命中。先记住这个默认行为;让匹配忽略大小写的修饰符 nocase 在下一课展开。
载荷里不是所有字节都能直接打出来:空字节、控制字符、不可打印字节都需要另一种表达方式——十六进制。
在双引号内,用 | 包住十六进制字节序列,每个字节写成两个十六进制数字。Suricata 官方 quickstart 用这种写法检测 root 登录回显:
alert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7; metadata:created_at 2010_09_23, updated_at 2010_09_23;)|28| 是字节 0x28,也就是左括号 (;|29| 是字节 0x29,也就是右括号 )。整段 content 实际查找的文本是 uid=0(root)。文本与十六进制可以混写在同一个 content 里,这意味着你能用一条规则同时表达可打印与不可打印字节。
一条规则可以写多个 content。它们之间是“与”的关系:载荷必须同时包含每一个条件,规则才命中。
alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"多特征组合"; content:"admin"; content:"passwd"; sid:1002002; rev:1;)两个 content 都要在载荷中出现,缺一个就不告警。单特征容易误伤正常流量,组合多个字面量能把命中范围收得更窄。下一课你会学到 depth、offset、nocase 等修饰符,它们进一步收紧 content 的命中位置与大小写行为。
写 content 之前,先用 Wireshark 打开 PCAP,找到目标流量,逐字节确认载荷里到底有什么。官方社区推荐的完整流程是:分析威胁 → 写规则 → 用 -r 回放 PCAP → 检查 fast.log。先观察真实字节再写规则,能省下大量调试时间。
本课所有验证都在本地 PCAP 与授权流量上进行。不要对未授权的真实目标发起扫描或探测,也不要用规避手段绕过检测。防御性检测的练习边界是:自己抓的包、靶场流量,以及明确授权回放的数据。
动手:用 content 匹配 ASCII 与十六进制载荷
- 1
新建规则文件 custom.rules,写入一条 ASCII 匹配规则。
textalert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ASCII content test"; content:"GET"; sid:1002101; rev:1;) - 2
用测试模式校验语法。配置指向 /etc/suricata/suricata.yaml,规则文件用 -S 指定;测试模式沿用上一课的命令。
bashsuricata -T -c /etc/suricata/suricata.yaml -S /path/to/custom.rules - 3
回放 PCAP 验证。确认 suricata.yaml 里 fast 日志已启用,否则告警不会写进 fast.log。
bashsuricata -r attack.pcap -S /path/to/custom.rules - 4
把 content 改成小写 get,重新回放同一 PCAP,观察告警是否消失。这一步验证默认大小写敏感。
textalert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ASCII content test"; content:"get"; sid:1002101; rev:1;) - 5
再写一条十六进制规则,验证二进制字节匹配,复制下面的官方 quickstart 示例。
textalert ip any any -> any any (msg:"GPL ATTACK_RESPONSE id check returned root"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:2100498; rev:7;) - 6
查看 fast.log,核对告警行里的 sid、msg 与时间戳。
你应该看到
-T 无报错退出;回放后 fast.log 出现 sid 1002101 的告警行。把 content 改成 get 后,同一 PCAP 不再产生该告警,体现默认大小写敏感。包含 uid=0(root) 字节序列的流量被 sid 2100498 命中。若 PCAP 里本就没有这些字面量,没有告警同样是正确结果。
遇到问题:规则通过 -T 语法检查,但回放 PCAP 后 fast.log 里没有对应告警。
可能原因
- 载荷中实际没有你写的字面量。content 默认区分大小写:get 不会命中 GET。
- 十六进制字节写错,实际字节序列与载荷不符,例如把 |28| 写成 |29|。
- fast.log 没有在 suricata.yaml 中启用,告警被写进 eve.json 而不是 fast.log。
- 回放时没有用 -S 指定自定义规则文件,Suricata 加载的是默认规则集。
排查步骤
- 在 Wireshark 里逐字节查看载荷,把真实字节复制进 content。
- 对照十六进制视图核对字节值,重写 |xx| 序列。
- 检查 suricata.yaml 的 fast 日志配置;同时用 eve.json 核对告警记录。
- 用 -S 显式指定规则文件,重新回放后再检查 fast.log。
自检:content:"uid=0|28|root|29|" 在载荷中匹配哪段文本?content 的匹配默认区分大小写吗?
匹配文本 uid=0(root)。|28| 表示十六进制字节 0x28,即左括号 (;|29| 表示 0x29,即右括号 )。content 默认区分大小写:GET 不会命中 get。让匹配忽略大小写由下一课的 nocase 修饰符实现。