用 tcp.flags 检测异常 TCP 标志组合

约 35 分钟

能用 tcp.flags 关键字检测非法或可疑 TCP 标志组合,并回放 PCAP 验证告警。

核心概念

tcp.flags:检查 TCP 控制标志

上一课我们用 flowbits 把两条规则在同一 flow 上串成一条证据链:先静默打标记,再按标记告警,并用 flow_id 核对了这两个阶段确实来自同一次会话。那解决的是跨规则的关联问题;而不少攻击特征并不需要关联两条规则——单个报文自己的状态信息就足以暴露问题。于是我们把视线从 flow 之间收回到 flow 内部,检查 TCP 报文头里自带的一组控制标志。

把 TCP 报文想成一扇双向门,门上有几盏状态灯:S(SYN,请求开门)、A(ACK,确认收到)、F(FIN,请求关门)、R(RST,强行关闭)。正常使用中,灯只按流程点亮。若同一份请求既亮 S 又亮 F——同时要求开门和关门——你会立刻怀疑按钮被人做了手脚。

tcp.flags 是规则选项,用来检查 TCP 报文头里的控制标志位。标志用字母表示:S=SYN、A=ACK、F=FIN、R=RST,其余标志同理。规则给出字母组合时,要求这些标志全部置位;置位就是该位为 1。例如 tcp.flags:SF; 要求同一报文同时置位 SYN 与 FIN。

判断一个组合是否异常,先对照协议状态机:三次握手和挥手分别在哪些阶段亮哪些灯,凡是正常流程讲不通的亮法都值得单独成规则。SYN 单独出现是正常的,SYN 与 FIN 同现则不是;合法会话不会发出这种组合,所以它常被扫描器用来探测主机、试探防火墙。tcp.flags 的匹配对象是单个报文而不是整个会话;一条规则负责一种组合,组合写得越具体,规则越精确。

正常 TCP 会话的流程是固定的:三次握手依次为 SYN、SYN+ACK、ACK;正常关闭使用 FIN/ACK;异常中断用 RST。任何合法流程都不会把“开始连接”的 SYN 与“结束连接”的 FIN 放进同一个报文。同理,SYN+RST,以及同时置位多个无关标志的所谓 XMAS 报文,也只出现在构造流量中。

可以这样记忆正常流程:握手 = SYN / SYN+ACK / ACK,挥手 = FIN+ACK / ACK,重置 = RST。凡是不属于这三类的标志组合——例如 SYN+FIN、SYN+RST、无前置握手的纯 ACK、XMAS(FIN+PSH+URG 同开)——都应先怀疑再放行。

扫描器发出这类报文后观察对方是否回 RST,据此推断端口状态。部分只统计纯 SYN 的检测方案会被这类组合绕过,因此异常标志组合值得单独成规则。

参考规则如下。它把方向限制为 to_server,只检查发往受保护网络的报文,让应答方向的匹配不进入检测,从而减少噪声:

text
alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP SYN+FIN abnormal flags"; tcp.flags:SF; flow:to_server; classtype:attempted-recon; sid:4000001; rev:1;)

规则体里的新内容是 tcp.flags:SF; 与 flow:to_server;:前者要求 SYN 与 FIN 同时置位,后者限定方向。msg、classtype、sid、rev 的写法与前几节一致,此处不再展开。

方向限制是控制误报的第一道闸门:同样的 SYN+FIN 报文,若不做方向限制,两个方向都匹配会让告警数翻倍,也更容易混入正常会话的镜像流量。规则头 $EXTERNAL_NET any -> $HOME_NET any 表示只看外部发向内部的报文;若你的抓包方向相反,把箭头两侧对调后再回放。tcp.flags 只描述这个报文带哪些标志,不关心它属于哪个 TCP 状态;判断异常要靠你自己对照状态机,所以方向要收紧、组合要写具体。

编写规则、回放 PCAP 并核对告警

  1. 1

    确认 fast.log 已启用。前几节若已配置就直接沿用;否则在 suricata.yaml 中启用 fast 输出,并重启 Suricata 使其生效。

  2. 2

    在自定义规则文件末尾追加 SYN+FIN 检测规则。文件路径沿用你前几节的约定,例如 /etc/suricata/rules/local.rules。

    shell
    alert tcp $EXTERNAL_NET any -> $HOME_NET any (msg:"SCAN TCP SYN+FIN abnormal flags"; tcp.flags:SF; flow:to_server; classtype:attempted-recon; sid:4000001; rev:1;)
  3. 3

    用测试模式校验语法。无报错且正常退出,规则即可用。

    bash
    suricata -T -c /etc/suricata/suricata.yaml -S /etc/suricata/rules/local.rules
  4. 4

    回放课程提供的授权 PCAP(把路径替换为你环境的实际位置)。该抓包应至少包含一个 SYN+FIN 报文,并混有正常会话用于对照。

    bash
    suricata -r /home/student/lab4/synfin.pcap -S /etc/suricata/rules/local.rules -l /var/log/suricata
  5. 5

    查看 fast.log,找到该 sid 的告警行,核对 msg、源目地址与时间。

    bash
    tail -n 20 /var/log/suricata/fast.log
  6. 6

    用同一规则回放一段不含异常标志的正常会话(例如前几节留下的 HTTP 抓包),确认 fast.log 没有新增该 sid 的告警。

你应该看到

回放异常 PCAP 后,fast.log 中该 sid 的告警条数与异常报文数一致;回放正常会话时无新增告警。若正常会话也命中,说明方向限制或标志组合过宽,收紧 flow 与 tcp.flags 后重试。

遇到问题:回放含异常标志的 PCAP 后,fast.log 没有出现预期告警。

可能原因

  • 规则语法错误:suricata -T 报错退出,常见于标志字母拼写错误、引号或分号缺失。
  • 规则未加载:-S 指向的文件不包含该 sid,或该文件未列入 suricata.yaml 的规则列表。
  • fast.log 未启用,或日志目录与查看位置不一致。
  • PCAP 中没有 SYN+FIN 报文,或报文方向与 flow:to_server 不符。

排查步骤

  1. 先运行 suricata -T 逐条修复语法错误,直到测试模式正常退出。
  2. 确认 -S 指向的文件确实包含目标 sid;查看 Suricata 启动日志,确认该规则已被加载。
  3. 在 suricata.yaml 中启用 fast 输出,回放后到 /var/log/suricata 查看 fast.log。
  4. 用 Wireshark 打开 PCAP,以 tcp.flags 显示过滤定位 SYN+FIN 报文,核对方向与地址,然后调整规则头或 flow 再回放。
自检:你的规则使用 tcp.flags:SF;。三次握手的报文依次是 SYN、SYN+ACK、ACK,这三条会被命中吗?为什么?

不会。tcp.flags:SF; 要求 SYN 与 FIN 同时置位,而三次握手的任何一条报文都不满足:SYN 只有 S,SYN+ACK 是 S 加 A,ACK 只有 A。只有构造或异常的报文才会同时带 S 与 F,因此命中即代表可疑。若再加 flow:to_server,还能排除应答方向的匹配,进一步降低噪声。