跳到主要内容

规则裁剪 20% 的取舍:判据、演练与代价

2026-10-13安全运营 7 分钟阅读 3348 字53,122 条 alert 规则、44MB 规则文件,24 小时里真正命中的只有 1 个签名。裁掉 10,675 条(约 20%)之后,阳性对照照常在报、基线命中集逐条复现、丢失 = 0。这篇讲判据怎么定、怎么证明没裁错、以及被裁掉的那部分弱信号意味着什么。

起因:付费的是加载,不是命中

先把数字摆出来(2026-10-13 实测):

单文件 suricata.rules:69,080 行 / 53,122 条 alert 规则(44 MB)
近 24h 实际命中:只有 1 个签名 —— ET INFO Python-urllib(约占该组 97%)

读法很直接:规则文件 44 MB、五万多条 alert 规则, 而它们几乎每天都在"被加载、被匹配",然后什么也不报。

  • 加载开销:每次启动、每次热加载;
  • 匹配开销:每一个包都要过一遍;
  • 运维开销:真出告警时,人要在噪声里找那条真的。

所以裁剪的目标不是"规则越少越好",而是:

把"从不命中的 Informational 噪声"摘出去,同时保证在用的检测一个不丢。


判据:三条原则,一条都不能松

裁剪原则(三条同时满足才进候选):

  1. 24h 无命中;
  2. 不在演练覆盖集里(演练要用到的签名一律保留);
  3. 非高危类别。

这里有一个我犯过的错,必须写出来

我用过一版错误判据:按消息文本搜关键词(“Malware”/“Trojan”/“exploit”)。

这是错的,原因很实在:消息文本里出现这些词,不代表它属于高危类别。 实测里消息文本带这些字样的 21 条,逐条看下来全是 ET INFO / FILE_SHARING 低危规则。

正确判据是按 classtype 核。 候选集的 classtype 分布实测是:

bad-unknown        6553
misc-activity      3498
policy-violation    434
external-ip-check   137
not-suspicious       53
→ 无一条属真正的 MALWARE / EXPLOIT / TROJAN / C2 规则类

一般化教训:用结构化字段做安全判断,不用文本匹配。 文本匹配看起来更快,但它会把"描述里提到这个威胁"和"这条规则在防这个威胁"混为一谈 —— 在裁剪这种不可逆动作上,这个混淆的代价是全盲。


怎么证明"没裁错":三类证据

裁剪是一类很难回滚得当的操作(规则删了、告警少了,你很难知道少了什么)。 所以证据必须是结构化的、可复现的,不能是"我看了一眼没问题"。

证据一:生产唯一在响的签名还在

sid 2013031(ET INFO Python-urllib)  仍在 ✓
另 2001219(ET SCAN SSH)/ 2002910 / 2010935 / 2001569 / 2013028  全部保留 ✓

证据二:阳性对照 —— 重启后它照常在报

这一步是最关键的一步,因为它验证的是"运行时真的用上了新规则集":

2026-10-13 09:47:26 CST:重启 suricata 容器加载裁剪后的规则(维护窗口内,已获批准)
  · 容器内规则 58,405 行 / alert 42,447 条 ✓
  · 解析/配置错误 0 条 ✓
  · 阳性对照:生产里持续命中的 ET INFO Python-urllib (sid 2013031) 重启后照常在报
    (fast.log 01:48:51 / 01:48:56 UTC 两条,节奏与裁剪前一致)→ 裁剪未伤及在用检测 ✓
  · eve.json 持续写入 ✓

为什么说是"阳性对照"而不是"检查配置":配置正确 ≠ 运行时加载了。 只有在新规则生效之后看到那条已知会命中的告警继续出现, 才能证明"引擎确实在用这份新规则集,而且它还能工作"。

一个负面对照同样重要:如果重启后它不报了, 那你分不清"是裁剪裁坏了"还是"这段时间真的没流量"。 没有阳性对照的验证等于没有验证。

证据三:新旧引擎对照演练,丢失 = 0

实施方另做 4 次对照演练(本地轮 / srv1 轮 × 旧引擎 / 新引擎):
  基线命中集 {2228000, 2260000} 在裁剪后【逐条复现、丢失 = 0】
indexer 侧独立复核:2013031×96 | 2228000×24 | 2260000×12 | 2001219×1(吻合)
裁剪后 24h 命中集合仍为 58 个,且与裁剪前逐项一致

最后一行是这次裁剪里我最看重的数字:58 个命中项,逐项一致。 不是"总数差不多",而是集合相等 —— “总数差不多"可以掩盖"丢了两个、多了两个”,集合相等不行。


结果

alert 规则:53,122 → 42,447(-10,675,约 -20%)
ET INFO 类:5,341 → 1,150 条
备份:/opt/suricata/backup/suricata.rules.bak-20261013-093222(原始 69,080 行)
候选留档:prune-candidates-*.tsv(1.1 MB)/ prune-candidate-sids / prune-keep-sids

“候选留档"这一项我想单独强调:裁剪决策的全过程(哪些进了候选、哪些明确保留) 都留成了文件。理由是:半年后有人问"为什么这条规则没了”,答案应该在仓库里,不在我脑子里。


代价:被裁掉的是弱信号,不是噪声

诚实写清另一面。被裁掉的确实包含一些弱信号,例如:

  • “HTTP 请求 .su TLD 常与恶意软件相关”
  • “可执行文件从 /tmp 目录提供”
  • “Windows 98/3.1 User-Agent 检出”

它们 24h 从未命中,但"从未命中"不等于"永远不会命中"。 取舍理由是:这几类信号在本环境里从未出现过,却在每次加载/匹配上都要付费。

有三条缓解措施让这个取舍可控:

  1. 备份在:suricata.rules.bak-20261013-093222 是裁剪前的完整 69,080 行;
  2. 候选集留档:prune-candidate-sids 记录被裁的 sid,可按需只加回特定的一批;
  3. 不是永久决定:将来需要这类信号,恢复或按需加回都行。

一个诚实的判断:这次裁剪降低的是"在本环境里永远不命中的规则的持有成本", 代价是"如果出现这类攻击、而它只在被裁的规则里有特征,我们会漏"。 这个代价我认为值得,但它不是零 —— 写在这里,不粉饰。


三个必须记住的操作事实

裁剪过程中撞到的三个坑,全部写进文档,避免下一个人重新踩:

① 真实检测空窗 ≈ 41–42 秒,不是 5–10 秒。 (Signal Received → Engine started。)而且本配置关闭了 console 日志, docker logs 里看不到 “Engine started” —— 必须以 suricata.log 的时间戳为准。 今后在维护窗口里重启 suricata,按 45 秒空窗规划。

② 不要在 512 MiB 的 cgroup 里执行 docker exec suricata suricata -T。 那会在同一个 cgroup 内再起一个 450–485 MiB 的引擎,与在线引擎叠加有 OOM 风险 (实测侥幸未触发)。 → 校验规则请用一次性独立容器(把 rules + yaml 挂进去跑 -T),或先停在线引擎。

③ 09:47:15 那次"非实施方发起的 docker-API 重启"是我做的(为了加载裁剪后的规则), 不是 OOM、不是异常。 这一条是"把动作写进日志"的价值:否则半年后有人看到这条重启记录, 会以为当时出了故障。


一个未决项:那个从未生效的 threshold.conf

这一条我在蜜罐那篇里也提过,但值得在这里再写一遍,因为它是一个典型的"验证不完整":

compose 只挂载 etc/suricata.yaml、rules、logs
→ threshold.conf 不在挂载之列
→ 它只是镜像层里一个 0 字节文件(/etc/suricata/threshold.conf,Oct 12 16:08)

也就是说:“sid 2001219 阈值抑制"从未真正生效。 当初只把 yaml 的 threshold-file: 指过去,没把抑制规则写进那个文件, 而"文件内容"这一层当时没验。

**修法(若启用)**在这里写清,站主确认前不动:

  1. 把抑制规则写到已挂载的目录(如 /opt/suricata/rules/threshold.conf);
  2. yaml 的 threshold-file 指向 /var/lib/suricata/rules/threshold.conf;
  3. 备份 → 改 → 用一次性容器跑 -T 校验(见上面第 ② 条);
  4. 重启(按 41s 空窗规划);
  5. 确认日志里 Threshold config parsed: N rule(s) found 的 N > 0。

第 5 条是这个修法的核心:放行条件必须是"引擎自己说它读到了几条阈值规则”, 而不是"我把文件写对了"。


小结:裁剪的纪律清单

步骤必须做到
定判据用结构化字段(classtype),不用消息文本关键词
保留集演练覆盖集 + 在用签名,一律先保留
阳性对照重启后,已知会命中的签名必须继续出现
集合对照对比"逐项集合",不是"总数差不多"
留档备份 + 候选 sid + 保留 sid,全部入库
放行条件引擎的退出码 / 引擎自己打印的计数,不是人工判断
操作事实空窗时长、cgroup 内存限制、谁按了重启,全部写下来

一句话:安全上的"减法"比"加法"更需要证据 —— 你删掉的东西不会再来告警提醒你它被删了。


本文的规则条数、sid、classtype 分布、耗时与文件路径均来自本项目的执行记录与 RUNBOOK.md,未做美化。