在系统调用层面抓,就撒不了谎
一句话总结
Falco 是从内核接收系统调用并与规则比对的运行时检测引擎。一条规则由 rule、desc、condition、output、priority 五项构成,重复出现的条件用 macro 抽出,值的列表用 list 抽出,以便复用。
为什么需要它
基于日志的检测有一个结构性的局限。日志只会留下程序决定要留下的内容。 如果入侵者以不留日志的方式行动——比如把二进制文件丢到 /tmp 然后直接执行——任何应用都不会记录它。
系统调用则不同。无论是创建进程、打开文件还是连接套接字,都必须经过内核,而内核是骗不了的。所以在询问“实际发生了什么”时,系统调用是最底层的位置。内核审计(auditd)看的也是同一个位置,但如果说 auditd 是为每条规则决定留不留记录的记录装置,那么 Falco 就是评估条件后决定现在要不要发告警的判定装置。两者不是替代关系,而是一对。
工作原理
规则的基本要素整理如下。必填的是 rule(简短且唯一的名称)、desc(抓什么)、condition(应用于事件的过滤表达式)、output(命中时输出的语句)、priority(严重程度)这五项,可选的有 enabled、tags、source、exceptions。priority 可用的值有八个——EMERGENCY、ALERT、CRITICAL、ERROR、WARNING、NOTICE、INFORMATIONAL、DEBUG。
- list: outbound_clients
items: [curl, wget, nc]
- macro: from_scheduler
condition: (proc.aname[2]=cron or proc.aname[3]=cron)
- rule: Scheduled job made an outbound request
desc: 예약 작업 밑에서 외부 요청 도구가 실행됐다
condition: spawned_process and proc.name in (outbound_clients) and from_scheduler
output: "예약 작업이 바깥으로 (user=%user.name proc=%proc.name cmd=%proc.cmdline)"
priority: WARNING
tags: [labhub, cron]
list 和 macro 在顺序上有约束。两者都只能引用定义在自己前面的内容。所以在文件中按列表 → 宏 → 规则的顺序书写就成了习惯。像 spawned_process 这样只出现名称的条件,是 Falco 一并发布的默认宏。在这个实验 VM 上实测,如果只把自己的规则文件交给 falco --validate,会因 Undefined macro 'spawned_process' 而失败,必须同时给出默认规则文件才能通过——这说明验证也必须在与加载相同的条件下进行。
条件中可以使用的字段名在支持的字段列表中。在进程相关字段中特别有用的是祖先名称。proc.pname 只是父进程一个,而 proc.aname[2] 指的是祖父进程。在 cron 启动 shell、shell 再调用 curl 的结构中,父进程是 sh,所以必须看到祖先,才能说“它是从计划任务里出来的”。
告警发往哪里由输出设置决定。默认是标准输出,所以在 systemd 下会进入 journal,但如果早上要用机器来统计,改成 JSON 并同时落到文件会更好。输出语句中的 %proc.cmdline 之类的占位符,整理在输出格式文档中。
减少误报的位置是 exceptions(例外文档)。在 fields 中写要看的字段,在 comps 中写比较方式,在 values 中写值的集合,Falco 就会把它们以 and not (…) 的形式接到条件后面。结果与直接修改条件字符串相似,但有两点不同——例外与规则是分离的,之后可以通过覆盖只更换值;并且能看清楚排除了什么、为什么排除。
在现场相遇的样子
最常见的失败是把例外融进条件里。如果把 and not proc.cmdline contains backup 加在条件末尾,六个月后没人知道那一段是为了什么。而且这样加上的例外通常太宽——含有 backup 这个词的所有命令都会变得安静。
第二种是规则开着却不看输出。在这个实验 VM 上保持默认设置,告警只会流向 journal,VM 一消失,告警也随之消失。告警不去文件或收集器的检测,只会带来“它在运行”的安心感。
第三种是优先级通胀。把所有规则都设为 CRITICAL,级别就不再是信息了。把该在凌晨叫醒人的和早上汇总看看就行的区分开,是编写规则工作的一半。
下一项实验要做什么
实验中要在装有 Falco 的 VM 上亲自编写规则。拆分列表和宏,把输出改成 JSON 文件,实际种下标题中的事件(计划任务每分钟向外发出请求),在文件中确认规则抓到了它。然后再制造一个会命中同一规则的正常作业,只用 exceptions 把它排除,再确认坏的仍然能被抓到。系统调用插桩是内核功能,所以本实验也在 VM 中运行。