4. 签名检测引擎:Snort规则语法、规则头与规则选项、编写自定义检测规则

说到入侵检测,Snort 几乎是绕不开的名字。我入行那会儿,第一件事就是啃 Snort 规则手册。说实话,刚开始觉得它像天书,后来才发现,这玩意儿其实就是一张“攻击画像”。你画得像,它就能抓得准。

这一节,我们就来聊聊怎么画这张画像。

4.1 Snort 规则长什么样?

一条完整的 Snort 规则,分成两部分:规则头 和 规则选项。中间用圆括号包起来。我习惯把它比作“谁 + 干了什么 + 怎么处理”。

看个例子:

alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (msg:"HTTP 请求检测"; sid:1000001; rev:1;)

拆开来看:

  • alert:动作。告诉引擎,匹配到了就报警。
  • tcp:协议。只检查 TCP 流量。
  • $EXTERNAL_NET any:源 IP 和端口。来自外网,任意端口。
  • ->:方向。从左到右。
  • $HOME_NET 80:目的 IP 和端口。去往内网,目标端口 80。
  • msg、sid、rev:规则选项。描述信息、规则 ID、版本号。

嗯,结构其实很清晰。你想想看,一条规则就是一句话:“如果外网有人往我的 80 端口发 TCP 包,就报警。”

4.2 规则头:谁在跟谁说话

规则头定义了流量的“五元组”骨架。我见过不少新手,一上来就写复杂的选项,结果规则头写错了,根本匹配不到流量。

字段 说明 常见取值
动作 匹配后做什么 alert、log、pass、drop、reject
协议 检查的协议类型 tcp、udp、icmp、ip
源IP 发起方地址 any、$HOME_NET、192.168.1.0/24
源端口 发起方端口 any、80、[80,443]
方向 流量流向 ->、<>
目的IP 接收方地址 any、$HOME_NET、!$DNS_SERVERS
目的端口 接收方端口 any、21、[1:1024]
我的习惯: 尽量用变量名(比如 $HOME_NET)代替硬编码 IP。这样换环境时,改一个配置文件就行,不用翻几百条规则。

4.3 规则选项:攻击的“指纹”

规则选项才是真正的检测逻辑。Snort 提供了上百个选项,但常用的也就二三十个。我个人把它们分成三类:

  • 元数据类: msg、sid、rev、classtype、priority。用来标记规则本身。
  • 内容匹配类: content、uricontent、pcre。这是核心,用来匹配攻击载荷。
  • 流量特征类: flags、dsize、ttl、itype、icode。用来匹配协议头的异常。

举个例子,检测一个简单的 SQL 注入尝试:

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
  msg:"SQL 注入 - 检测 'or 1=1'";
  content:"'or 1=1|3D|1|3D|1|20|--|20|";
  nocase;
  sid:1000002;
  rev:1;
)

这里 content 后面的十六进制,其实是把 'or 1=1=1 -- 做了编码。为什么要编码?因为有些攻击者会混淆大小写,或者用 URL 编码绕过。Snort 的 content 选项支持原始字节匹配,这是它的杀手锏。

我曾经踩过的坑: 写 content 时忘了加 nocase。结果攻击者把 "OR" 改成 "Or",规则就放过了。从那以后,只要匹配字符串,我必加 nocase。

4.4 编写自定义检测规则

理论说完了,咱们动手写一条。假设我们要检测一个“目录遍历攻击”,攻击者试图通过 ../../../etc/passwd 读取系统文件。

第一步,确定规则头。攻击者从外网来,目标是我们内网的 Web 服务器,端口 80 和 443。

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS

第二步,写规则选项。核心是匹配 ../ 这个模式。但要注意,攻击者可能会用 ..%2f 或者 ..%5c 来绕过。

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
  msg:"目录遍历攻击 - 检测 ../ 模式";
  content:"../";
  nocase;
  sid:1000003;
  rev:2;
)

等等,这样够吗?不够。因为正常的 URL 里也可能出现 ../,比如 /images/../index.html。我们需要更精确一点。

改进一下,用 uricontent 只匹配 URI 部分,再加一个 pcre 做正则校验:

alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS $HTTP_PORTS (
  msg:"目录遍历攻击 - 深度检测";
  uricontent:"../";
  pcre:"/(\.\.\/){2,}/i";
  nocase;
  sid:1000004;
  rev:1;
)

这个 pcre 的意思是:匹配连续出现两次或以上的 ../。这样,正常的单次跳转不会被误报,而攻击者的深度遍历就会被抓住。

核心思路: 规则不是越复杂越好,而是越“精准”越好。宁可漏掉一个变种,也不要产生一千个误报。误报多了,运维人员会直接关掉你的规则。

4.5 规则调试与优化

写完了规则,怎么验证它好不好用?我一般用三步:

  1. 语法检查: snort -T -c snort.conf。看有没有报错。
  2. 回放测试: 用 snort -r 攻击流量.pcap -c snort.conf -A console。看规则能不能触发。
  3. 压力测试: 用正常流量跑一遍,看误报率。我见过最夸张的一次,一条规则在线上每分钟产生 2000 条告警,全是误报。

为什么会这样?因为规则写得太宽泛了。比如只匹配了 content:"select",结果所有包含 "select" 的 HTTP 请求都被报警了。你想想看,一个正常的搜索页面,用户搜 "select * from",这算攻击吗?

所以,优化规则的关键是:缩小匹配范围,增加上下文约束。

优化方向 做法 示例
缩小协议范围 指定具体端口 从 any 改为 $HTTP_PORTS
增加内容约束 用 distance、within 控制相对位置 content:"A"; content:"B"; distance:0;
使用 pcre 做精确匹配 正则比 content 更灵活 pcre:"/union\s+select/i"
添加流量特征 检查包大小、标志位 dsize:>1000; flags:A+;

下面这张图,是我自己总结的 Snort 规则编写流程,你可以参考一下:

Snort 自定义规则编写流程 Snort 自定义规则编写流程 1. 确定攻击特征 2. 编写规则头(动作/协议/地址) 3. 编写规则选项(content/pcre) 4. 语法检查(snort -T) 5. 回放测试(pcap 验证) 💡 提示:每步都要检查误报率,误报率 > 1% 的规则不建议上线 优化方向:缩小端口范围、增加 content 约束、使用 pcre 精确匹配

4.6 避坑指南

最后,分享几个我这些年总结的教训:

  • 不要迷信公开规则集。 公开规则集覆盖的是通用攻击,但你们公司的业务系统可能有自己的“特色漏洞”。我建议,至少 20% 的规则应该是自定义的。
  • sid 要规划好。 公开规则集的 sid 范围是 1~1000000。自定义规则建议从 1000001 开始,避免冲突。
  • 别忘了 rev。 每次修改规则,rev 加 1。不然你都不知道线上跑的是哪个版本。
  • 测试环境要模拟真实流量。 我曾经在测试环境用 curl 发包,规则全过。结果上线第一天,被正常业务流量打爆了。因为测试流量太“干净”了。

嗯,Snort 规则这东西,说难不难,说简单也不简单。关键是多写、多测、多优化。等你写过几百条规则之后,看到攻击流量,脑子里自然就会浮现出对应的规则模板。