一条规则改写六次
目标
针对 24 条合成的进程执行记录编写六条 Sigma 规则。每次都实际运行,用数字确认命中了几条、漏掉了几条,据此逐步收窄规则。
为什么重要
检测规则在现场死掉,原因通常不是语法。规则太宽,误报堆积,人们就不再看告警;规则太窄,只要一个地址变了就什么也抓不到。找到二者之间位置的唯一办法是拿数据去跑。写完规则,用眼睛读一遍觉得“应该能命中”就部署的习惯,是检测缺口最大的成因。
所以本实验每写一条规则,就把它应用到事件记录上。命中的条数和误抓的条数以数字给出后,“加上这个条件会把什么一起带进来”,就不是凭感觉,而是通过观察得知的。这就是检测工程在安全工作中格外像软件工程的原因——规则是代码,事件记录是测试数据。
步骤
- 按父进程统计事件记录的条数,保存到
/root/sigma/01-parents.txt。 - 把以 cron 为父进程的执行全部抓住的最小规则写到
/root/sigma/02-cron-any.yml。 - 收窄为只抓命令行中含有
curl或wget的,写到/root/sigma/03-outbound.yml。selection 只写一个。 - 用过滤器排除发往内部仓库(
repo.corp.internal)的正常备份,写到/root/sigma/04-filtered.yml。在condition中使用not。 - 不要写地址,而是用行为来抓的规则,写到
/root/sigma/05-behaviour.yml。有两种:把拉取到的内容通过管道交给 shell,以及挂上 shell 向外连接。必须使用|re修饰符。 - 把计划任务的外部请求和反弹 shell 拆成以
selection_开头的两个选择,用1 of selection_*合并,写到/root/sigma/06-combined.yml。内部仓库过滤器作为公共条件。 - 给第 6 步的规则补全发布用的元数据,写到
/root/sigma/07-release.yml。 - 把第 3 步和第 7 步的规则的判定结果,用数字和判断写到
/root/sigma/08-review.md。
参考
- 材料是
/opt/lab/fixtures/detection/proc_events.jsonl,一行是一个事件。像jq -r '.ParentImage' <파일>这样读取(占位符为文件名)。 - 配有运行规则的工具。
python3 /opt/lab/fixtures/detection/sigma_eval.py --lint <규칙>只检查结构,--ids <규칙> <사건파일>会输出命中事件的 id(占位符依次为规则文件、事件文件)。请用--help确认支持的范围。 - 常见错误:用
|contains查找很短的词。nc也包含在rsync里。 - 常见错误:把 selection 中以列表给出的值误以为是 AND。一个字段里的列表是 OR。
- 第 6–7 步的真值集(实际被入侵的 4 条)可以把第 5 步和第 3 步中看到的合起来得到。
先浏览数据
按父进程(ParentImage)统计 /opt/lab/fixtures/detection/proc_events.jsonl,保存到 /root/sigma/01-parents.txt。每行有一个路径和它的条数即可。
用 jq -r '.ParentImage' 只取出值,再用 sort | uniq -c 统计。这一步是要养成习惯:写规则之前先看看有什么、有多少。
最小的规则
把以 cron 为父进程的执行全部抓住的最小 Sigma 规则写到 /root/sigma/02-cron-any.yml。只要有必填项(title、logsource、detection)即可。
在 detection 下放一个命名的选择,并把这个名称写到 condition 中。事件的 ParentImage 值是什么,可以在第 1 步的结果中找到。
只要向外的
把第 2 步的规则收窄为只抓命令行中含有 curl 或 wget 的,写到 /root/sigma/03-outbound.yml。detection 中的选择只放一个。
在一个选择中写两个字段是 AND,给一个字段以列表形式提供值是 OR。要查找子串,就在字段名后面加修饰符。
排除一个正常作业
在第 3 步的结果中去掉发往内部仓库(repo.corp.internal)的正常备份,写到 /root/sigma/04-filtered.yml。不要收窄选择,而是另设过滤器,用 not 接到 condition 上。
在 detection 下放两个选择,起不同的名称。condition 的形式是 selection and not <필터이름>(占位符为过滤器名称)。
用行为而不是地址
把抓两种行为的规则写到 /root/sigma/05-behaviour.yml:把拉取到的内容通过管道交给 shell,以及挂上 shell 向外连接。使用 |re 修饰符,并且不要在规则中写目的地址。
正则修饰符的形式是 필드|re: '<정규식>'(占位符依次为字段、正则表达式)。请重新读一下第 1 步中看到的命令行,想想管道字符和 -e /bin/sh 这样的片段意味着什么。两个分支也可以拆成两个选择,再用 1 of 合并。
把两个分支合成一个
把计划任务的外部请求和反弹 shell 拆成以 selection_ 开头的两个选择,用 1 of selection_* 合并,写到 /root/sigma/06-combined.yml。内部仓库过滤器对两个分支都适用。
1 of selection_* 会把名称匹配该模式的选择用 OR 连接起来。过滤器以 and not 接到整个这一组上。目标是实际被入侵的 4 条。
变成可以发布的规则
给第 6 步的规则补全 id、status、description、author、date、references、falsepositives、level、tags,写到 /root/sigma/07-release.yml。抓到的事件必须与第 6 步相同。
id 必须是 UUID 的形式(python3 -c 'import uuid;print(uuid.uuid4())')。status 选用尚未拿生产数据运行过的规则所对应的值,level 则按能抓到反弹 shell 的规则来定。tags 中至少需要一项以 attack. 开头的条目。
用数字留下来
把第 3 步和第 7 步的规则与真值集(实际被入侵的 4 条)对照,写到 /root/sigma/08-review.md。每条规则占一行,写文件名和 matched=、tp=、fp=、fn= 四个值,并在 ## 판단(韩文,意为“判断”)一节中用 120 个字符以上写出要发布什么以及理由。
把 --ids 的结果与真值集比较,亲手数出这些值即可。真值集是第 5 步找到的两条,加上第 3 步找到的条目中不是发往内部仓库的那些。