← 配套笔记:一次可复现的紫队自测设计
XK-LAB // 紫队演练 PURPLE-TEAM ● 设计稿 · 2026-10-18 · 规则未部署

红队问「能不能打进去」,蓝队问「有没有看见」,紫队问的是两者对不对得上: 我打出去的这一下,是不是恰好那一条规则该响;打完之后日志里到底有没有它。 这一页把这件事做成一张可执行的设计稿 —— 三条攻击、每条一个明确的预期 SID、一套 grep 验证方法, 再把还没验的部分写成「未验证」,而不是写成「命中 0」。

口径 · 数字从哪来、什么算验过先看这块,再看数字
规则在哪detections/suricata/xk-lab.rules(7 条生效 + 1 条抑制型默认注释,SID 1000101–1000108)、 detections/wazuh/xk-lab_rules.xml(3 条,100400/100401/100402)。 两份都只做了语法验证:一次性 --rm 容器里 suricata -T 与 wazuh-analysisd -t,都是 exit 0,生产影响为零。
基线分母srv2 /opt/suricata/logs/eve.json,2026-10-11 09:23 ~ 2026-10-18 12:00 UTC, 共 1,471,376 行:alert 293,482 / flow 612,516 / http 283,796 / ssh 142,926 / dns 132,086 / tls 6,801。
阈值全部是从这份基线里统计出来的窗口分布,不是拍脑袋定的;改阈值前先重跑计数。
什么算「验过」一次自测只算成立,必须同时满足三件事:①规则文件真的在生产规则目录里且被加载; ②发起攻击后 grep 到对应 signature_id / rule.id; ③负样本对照不报(只打单端口的源不该触发「多端口」规则)。 缺第①条,grep 返回 0 说明的是没加载,不是没命中 —— 这两种结论完全相反。
本篇不引什么docs/Amadeus-项目总文档-20261017.md 的 §19 记的是 2026-10-18 的站点改动 (首屏聚合五块 / 发布链假失败 / 手机顶栏),里面没有任何 SID 与检测口径, 所以本页的数字一律不引它,只引 detections/README.md 与它引用的 eve.json 计数。

一句话口径:紫队自测的产物不是「拦住了多少」,而是 「哪条规则该响 / 实际响没响 / 没响是因为规则没加载还是真的没匹配上」这三件事分开记录。

三条可测攻击 · 各一条发起命令靶场自打 · 走内网

靶场的公网入口已整体撤下,SSH 面是蜜罐 :2222,所以要先 SSH 隧道进去或用内网机器发起。 这三条选的是「行为维度」而不是「载荷维度」—— 因为真实攻击里对方最爱伪装的东西(banner、UA)最不可靠。

#攻击发起命令(示例)预期命中为什么是它
1SSH 爆破for i in $(seq 1 10); do ssh -p 2222 [email protected] true; sleep 25; done1000103判据是会话速率(同源 300s 内 ≥10 次 SYN 到 :2222),匀速十次/五分钟就够;不是失败次数
2端口扫描nmap -sS -Pn -n -p 21,22,23,2222,3306 10.0.0.101000104
1000107
判据是端口要不同(60s 内 ≥3 个诱捕端口);打 DB 口再叠一条外部源直连规则
3Web 探测for u in /.env /.git/config /config.json; do curl -s -o /dev/null "http://10.0.0.10:8081$u"; done
再换 UA:curl -A 'zgrab/0.x' …
1000105
1000106
路径一条就够(意图明确),UA 那条只打标不封禁(UA 可伪造)

负样本必须一起跑:只打单端口、打几千次也不该触发 1000104 / 100401。 真实对照就在基线里 —— 179.63.84.194 的 2,752 次全落在 1433 一个口上,规则设计上它不该被「多端口」规则报出来。 只测「该响的响了」是自欺,还要测「不该响的没响」。

规则 → 预期命中 · SID 1000101–1000108 + Wazuh 100400–100402依据计数取自 detections/README.md
规则什么条件下该响基线里的依据(真实计数)已知误报
1000101SSH 客户端 banner 以 Go 开头ssh 事件 142,926 中 software_version:"Go" 75,390 条(52.7%)自研 Go 巡检脚本 / CI 健康检查
1000102banner 以 libssh/libssh2 开头libssh_0.9.3 38,955 条;Cowrie 侧 SSH-2.0-libssh2_1.11.1 3,275 次备份/发布脚本、授权扫描器
1000103同源 300s 内 ≥10 次 SYN 到 :22221,448 个 (源,300s) 窗口:max 29 / p99 27 / mean 8.18;≥20 只有 42 个(2.9%)内部自查脚本($EXTERNAL_NET 已排除)、蜜罐重启补偿连接
1000104同源 60s 内 ≥3 个诱捕端口(21/22/23/1433/2222/3306/6379/9080/27017)53 个 (源,分钟) 窗口、275 个外部源;47.96.106.187 一分钟 9 个端口背景测绘(Censys/Shadowserver 类),建议打标不封禁
1000105HTTP GET .env 家族 / .git/config / .git/HEAD / web.config / config.json.env* 262 次;/.git/config 13;/config.json 7;/web.config 6;/phpmyadmin/scripts/setup.php 9CI 拉 .env 做存在性校验;客户授权渗透
1000106UA 命中 Go Web Pentest Tool / l9explore / zgrab / CensysInspect / zmap / Infrawatch / Nmap NSE / AttackWatch-PoC-Scanner155 / 96 / 72 / 26 / 6 / 23 / 16 / 10 次资产测绘、第三方合规探针;只打标,不作封禁依据
1000107外部源 SYN 直连 1433/3306/6379/9200/270171433 告警 1,628 条 / 136 源;3306 756 / 230 源;6379 129 源;27017 67 源云厂商端口探活(量极小,白名单排除)
1000108抑制型:$HOME_NET → 169.254.0.4:80 established 走 pass(默认注释、opt-in)293,482 条 alert 里 278,405 条(94.9%)是这一条出站 POST /ca_report.cgi写错方向会把真实 C2 一起静音,故默认不启用
100400同源 300s 内 ≥20 次 cowrie.session.connect2026-10-17 单日 14,147 会话;窗口 max 29 / p99 27;≥20 仅 42 窗(2.9%),top 195.178.110.218(29)、45.148.10.161(28)内部自查脚本;10.0.0.10 需白名单
100401同源 60s 内触及 ≥3 个不同蜜罐端口flow 侧 53 个 (源,分钟) 窗口、275 个外部源;对照组 179.63.84.194 2,752 次全单端口 1433(本条不该误报它)背景测绘;docker 网关 172.19.0.1
100402蜜罐 MSSQL 收到凭据(credentials 键存在,已进认证阶段)credentials 事件 2,753 条(mssqld 2,750 / mysqld 3),其中 2,750 条来自 179.63.84.194蜜罐连通性自测(带假凭据),需排除

为什么 1000103 是 10/300s 而不是 20/300s:20 抓的是脉冲型源(29 次/5min 那种), 10 抓的是「稳、慢、持久」的源。真实源里 ≥20 的窗口只占 2.9%,用 20 会把大部分慢速爆破漏掉 —— 这不是阈值写错,是精度与覆盖率的分层取舍,自测时要按你想验的那一层选阈值。

怎么验证 · grep eve.json 与 alerts.json先确认加载,再数告警

第一步永远是确认规则被加载(否则后面的 0 没有意义):

# 语法验证(一次性容器,等价且不碰生产;README §3 记的就是这条)
docker run --rm --network none --pull=never -v /tmp/xkrules:/rules:ro \
  --entrypoint suricata jasonish/suricata:latest -T -S /rules/xk-lab.rules
# 结果:Suricata version 8.0.7 / Configuration provided was successfully loaded / exit 0
# 再看规则文件在不在生产目录(不在 ⇒ 后面 grep 出 0 是「没部署」)
ls -l /opt/suricata/rules/ ; ls -l /var/ossec/etc/rules/

第二步才是对账(Suricata 看 signature_id,Wazuh 看 rule.id):

# Suricata:先数条数,再只看自己这台源,避免被别人的扫描混进来
grep -c '"signature_id":1000103' /opt/suricata/logs/eve.json
grep '"signature_id":1000103' /opt/suricata/logs/eve.json | grep '"src_ip":"10.0.0.20"'
grep '"signature_id":1000104' /opt/suricata/logs/eve.json | tail -n 5

# 1000103 的输入证据在 flow 面:会话速率
grep '"dest_port":2222' /opt/suricata/logs/eve.json | grep '"event_type":"flow"' | tail -n 50

# Wazuh:alerts.json 一行一个 JSON
grep '"id":"100400"' /var/ossec/logs/alerts/alerts.json | tail -n 3
grep -c '"id":"100401"' /var/ossec/logs/alerts/alerts.json

判读规则:grep -c 非 0 且 src_ip 是自己 → 规则真的响了; 为 0 但规则已加载 → 规则没匹配上(要去看阈值和方向); 为 0 且规则没加载 → 未验证,不是「没命中」。

实测台账 · 命中 / 未命中的如实记录本次:未部署 ⇒ 全部未验证
规则部署状态发起过的攻击grep 结果结论
1000101 / 1000102未部署未发起—未验证(不能写成「没命中」)
1000103未部署未发起—未验证
1000104 / 1000107未部署未发起—未验证
1000105 / 1000106未部署未发起—未验证
1000108默认注释、未启用不需要发起(抑制型)—未验证(opt-in)
100400 / 100401 / 100402未部署未发起—未验证

所以答案是:看不见。 不是平台没看见我,是还没把眼睛装上去 —— 两份规则文件仍在 detections/ 旁路目录,没进 /var/ossec/etc/rules/ 与 /opt/suricata/rules/,也没 reload 过 suricata / wazuh-analysisd。 这一栏留白不是偷懒,是此刻唯一诚实的写法:没有非 0 的 grep,就没有「命中」这个词。

填这一栏的顺序:①部署两份文件 → ②suricata -T / wazuh-analysisd -t 通过后 reload → ③按上面三条打 → ④grep 记数(含负样本对照)→ ⑤只把有 grep 支撑的行改成「命中」或「没命中」。

三个已踩的坑 · 自测前先记住都是实测撞出来的,不是推的
失败次数型规则会空转已部署的 100110(120s 内 5 次登录失败)在 2026-10-17 全天 Cowrie 只有 19 条 login.failed 对 14,061 条 login.success, alerts.json 里 "id":"100110" 计数 0。 机器人拿泄露口令表,一枪就中 —— 新规则才改成按会话速率判(100400)。 自测时只发错误口令,测的其实是这条空转规则。
94.9% 的告警是自家的293,482 条 alert 里 278,405 条是 10.0.0.10 → 169.254.0.4:80 POST /ca_report.cgi。 自测前先想清楚:你 grep 出来的「命中」可能全是云主机心跳; 1000108 就是为消掉它写的,但它是 opt-in,写错方向会把真实 C2 一起静音。
Wazuh XML 的三种写法① JSON 解码事件不能用 <same_source_ip/>(内部 srcip 为空,规则永不触发) → 用 <same_field>src_ip</same_field>; ② <if_matched_group> 不写尾随逗号、<group> 要写; ③ 同一 rule 里 <decoded_as> 与 <regex> 不能共存 (Syntax error on tag 'regex' 会让 analysisd 拒绝启动)。
边界 · 这一页公开什么、不公开什么公开的是可复核的部分

公开:规则 id、判据(窗口 / 阈值 / 端口组)、每条规则背后的真实计数与窗口分布、 发起命令的形状、验证用的 grep、以及「未部署 ⇒ 未验证」这个结论本身。
不公开:生产环境的完整规则文件内容、真实服务器地址之外的内部拓扑、蜜罐真实口令与凭据、 以及任何还没部署就宣称「已生效」的说法 —— 最后这条不是安全问题,是诚信问题。

数字出处:detections/README.md(§0 侦察表、§1/§2 规则表、§3 语法验证、§4 边界) 与它引用的 /opt/suricata/logs/eve.json 统计。 规则本体在 detections/suricata/xk-lab.rules 与 detections/wazuh/xk-lab_rules.xml。 配套笔记见 《我打的攻击,我的平台能看见吗》。