用 flow 关键字控制会话方向与状态

约 25 分钟

能使用 flow 关键字限制规则在 established、to_server、to_client 等会话状态下匹配。

上一课你完成了从规则到验证的闭环:把恶意域名名单转成 dns.query 规则、用 tls.sni 编写可疑 TLS 域名规则,再用 suricata -r 离线回放 PCAP,对照 fast.log 与 eve.json 确认命中。那几类规则判断的是"连接去往哪里、协议字段长什么样";而这一课要回答"这条连接里,谁在对谁说话"。你已会用 content 匹配载荷里的字节。可同一个字节串,往往既出现在请求里,也出现在响应里。攻击者把命令发给服务器,载荷走向 to_server;服务器把执行结果回显给客户端,同样的字符串走向 to_client。只看载荷,分不清是谁在说话。

flow 关键字补上这个维度。它写在规则选项的括号内,让 Suricata 只检查某个会话方向、某个建立状态下的包。规则头里的方向箭头(->)描述源到目的,前面已经学过;flow 描述的是相对客户端与服务器的通信方向。两者配合使用。

核心概念

flow:给匹配加上会话方向与状态

生活类比:想象一段电话通话。你不仅要知道对方说了什么,还要知道是谁在说,以及电话是否已经接通。“客户端在说话”“服务器在说话”“接通之后的对话”——三件事彼此独立,又经常一起使用。

技术定义:flow 是规则选项括号内的关键字,多个值用逗号分隔,例如 flow:to_client,established;。to_server 限定匹配必须出现在发往服务器的通信中;to_client 限定匹配必须出现在发往客户端的通信中;established 限定只检查已建立连接中的包,从而减少无效匹配、降低引擎负载。三个值可以单独用,也可以组合,组合时用逗号连接。

flow 不负责匹配什么字节——那是 content 的职责;它只决定这段匹配发生在哪个方向、哪个状态。

方向判断最常见的用途,是区分请求与响应。Coralogix 的说明很直接:“如果我们要检测表明服务器已被入侵的服务器响应,用 flow 指出这段内容出现在 to_client 方向就很有用。”

很多攻击链路里,关键证据恰恰出现在响应方向:命令执行的回显、被篡改页面的返回、异常的错误页。这些字节串在请求方向几乎没有正常出现的理由。Suricata 文档自带一条现成的告警规则,可以拿来对照:

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;)

它没有 flow,也不限定协议,任何方向出现“uid=0(root)”都会告警。按同样的检测意图,收窄成 TCP 规则,并加上方向与状态:

text
alert tcp any any -> any any (msg:"ATTACK_RESPONSE uid=0(root) to client"; flow:to_client,established; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:100001; rev:1;)

改动只在选项区:多了 flow:to_client,established;。现在,只有已建立 TCP 连接中、服务器发往客户端方向的包出现该内容时,才会告警。

flow 与你已学的 content 修饰符各管各的。depth、offset、distance、within 决定某一段 content 在载荷里的匹配位置;flow 决定整条规则检查哪些包。两者不冲突,可以放心叠用。例如先由 flow:to_client,established; 过滤出已建立的响应包,再由 content 与 depth、within 在剩余包里精确定位特征。

一个实用的阅读习惯:看到一条陌生规则,先看括号里的 flow,确认它检查的方向与状态,再看 content 与修饰符。方向与状态先过滤掉大量无关包,剩下的载荷匹配才更有意义。

验证方向与状态约束

  1. 1

    准备一份本地授权 PCAP(靶场流量或自建请求/响应流量),并确认 suricata.yaml 中 fast.log 输出已启用。

  2. 2

    在自定义规则文件中加入两条规则:一条带 flow:to_client,established;,一条不带 flow 作对照(只改 msg 与 sid)。

    text
    alert tcp any any -> any any (msg:"ATTACK_RESPONSE uid=0(root) to client"; flow:to_client,established; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:100001; rev:1;)
    alert tcp any any -> any any (msg:"对照:uid=0(root) 任意方向"; content:"uid=0|28|root|29|"; classtype:bad-unknown; sid:100002; rev:1;)
  3. 3

    用你之前学过的测试模式(-T)检查两条规则的语法。若报错,先修正再继续。

  4. 4

    离线回放:suricata -r path-to-pcap(把 path-to-pcap 换成 PCAP 的实际路径)。

  5. 5

    查看 fast.log 与 eve.json,记录两条规则的告警分布。

  6. 6

    把规则里的 flow:to_client 改成 flow:to_server,重新回放,观察告警如何变化。

你应该看到

语法检查通过。回放后 fast.log 出现两条规则的告警:若 PCAP 中该字节串同时出现在请求与响应方向,带 flow 的规则只在响应方向(to_client)告警,对照规则两个方向都告警;若它只出现在请求方向,带 flow 的规则完全不告警。改成 to_server 后,告警方向随之反转。这些差异就是方向与状态约束的效果。

遇到问题:同一 PCAP 回放,规则加上 flow:to_client,established; 后不再产生任何告警。

可能原因

  • 匹配内容实际出现在请求方向(客户端发往服务器),而规则只允许 to_client 方向。
  • PCAP 只有握手或单方向流量,没有构成 established 会话,established 把整段流量排除在外。
  • fast.log 未在 suricata.yaml 中启用,或回放命令的输出路径与配置不一致。

排查步骤

  1. 用 Wireshark 检查该内容所在包的方向:由服务器发出的用 to_client,由客户端发出的用 to_server。在 Wireshark 里分析流量、再据此写规则,是 Suricata 官方论坛推荐的做法。
  2. 确认 PCAP 包含完整双向会话;若只有单方向流量,先去掉 established 只保留方向值,并在规则注释里说明取舍。
  3. 检查 suricata.yaml 中 fast.log 输出设置与回放命令的路径参数,必要时直接看 eve.json 确认告警是否生成。
自检:要检测“服务器被入侵后的响应内容”,规则应使用哪组 flow 值?为什么?

应使用 flow:to_client,established;。to_client 把匹配限定在服务器发往客户端的通信中,客户端请求里出现相同字节串时不会误报;established 限定只检查已完成握手的已建立连接中的包,握手包与孤立包不参与匹配,从而减少无效匹配并降低 Suricata 的负载。