用 content 关键字匹配原始与二进制载荷

约 25 分钟

能使用 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 会逐字节在载荷中搜索这个字面量。

text
alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"检测到明文 GET 请求"; content:"GET"; sid:1002001; rev:1;)

这条规则要求载荷中出现大写 GET 三个连续字节。默认大小写敏感,所以 get、Get、gEt 都不会命中。先记住这个默认行为;让匹配忽略大小写的修饰符 nocase 在下一课展开。

载荷里不是所有字节都能直接打出来:空字节、控制字符、不可打印字节都需要另一种表达方式——十六进制。

在双引号内,用 | 包住十六进制字节序列,每个字节写成两个十六进制数字。Suricata 官方 quickstart 用这种写法检测 root 登录回显:

text
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。它们之间是“与”的关系:载荷必须同时包含每一个条件,规则才命中。

text
alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"多特征组合"; content:"admin"; content:"passwd"; sid:1002002; rev:1;)

两个 content 都要在载荷中出现,缺一个就不告警。单特征容易误伤正常流量,组合多个字面量能把命中范围收得更窄。下一课你会学到 depth、offset、nocase 等修饰符,它们进一步收紧 content 的命中位置与大小写行为。

动手:用 content 匹配 ASCII 与十六进制载荷

  1. 1

    新建规则文件 custom.rules,写入一条 ASCII 匹配规则。

    text
    alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ASCII content test"; content:"GET"; sid:1002101; rev:1;)
  2. 2

    用测试模式校验语法。配置指向 /etc/suricata/suricata.yaml,规则文件用 -S 指定;测试模式沿用上一课的命令。

    bash
    suricata -T -c /etc/suricata/suricata.yaml -S /path/to/custom.rules
  3. 3

    回放 PCAP 验证。确认 suricata.yaml 里 fast 日志已启用,否则告警不会写进 fast.log。

    bash
    suricata -r attack.pcap -S /path/to/custom.rules
  4. 4

    把 content 改成小写 get,重新回放同一 PCAP,观察告警是否消失。这一步验证默认大小写敏感。

    text
    alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"ASCII content test"; content:"get"; sid:1002101; rev:1;)
  5. 5

    再写一条十六进制规则,验证二进制字节匹配,复制下面的官方 quickstart 示例。

    text
    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;)
  6. 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 加载的是默认规则集。

排查步骤

  1. 在 Wireshark 里逐字节查看载荷,把真实字节复制进 content。
  2. 对照十六进制视图核对字节值,重写 |xx| 序列。
  3. 检查 suricata.yaml 的 fast 日志配置;同时用 eve.json 核对告警记录。
  4. 用 -S 显式指定规则文件,重新回放后再检查 fast.log。
自检:content:"uid=0|28|root|29|" 在载荷中匹配哪段文本?content 的匹配默认区分大小写吗?

匹配文本 uid=0(root)。|28| 表示十六进制字节 0x28,即左括号 (;|29| 表示 0x29,即右括号 )。content 默认区分大小写:GET 不会命中 get。让匹配忽略大小写由下一课的 nocase 修饰符实现。