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] |
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 选项支持原始字节匹配,这是它的杀手锏。
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 规则调试与优化
写完了规则,怎么验证它好不好用?我一般用三步:
- 语法检查:
snort -T -c snort.conf。看有没有报错。 - 回放测试: 用
snort -r 攻击流量.pcap -c snort.conf -A console。看规则能不能触发。 - 压力测试: 用正常流量跑一遍,看误报率。我见过最夸张的一次,一条规则在线上每分钟产生 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 规则编写流程,你可以参考一下:
4.6 避坑指南
最后,分享几个我这些年总结的教训:
- 不要迷信公开规则集。 公开规则集覆盖的是通用攻击,但你们公司的业务系统可能有自己的“特色漏洞”。我建议,至少 20% 的规则应该是自定义的。
- sid 要规划好。 公开规则集的 sid 范围是 1~1000000。自定义规则建议从 1000001 开始,避免冲突。
- 别忘了 rev。 每次修改规则,rev 加 1。不然你都不知道线上跑的是哪个版本。
- 测试环境要模拟真实流量。 我曾经在测试环境用 curl 发包,规则全过。结果上线第一天,被正常业务流量打爆了。因为测试流量太“干净”了。
嗯,Snort 规则这东西,说难不难,说简单也不简单。关键是多写、多测、多优化。等你写过几百条规则之后,看到攻击流量,脑子里自然就会浮现出对应的规则模板。