LWN:在kernel内处理暴力尝试攻击!

共 3138字,需浏览 7分钟

 ·

2021-03-26 19:05

关注了就能看到更多这么棒的文章哦~

Handling brute force attacks in the kernel

By Jake Edge
March 17, 2021
DeepL assisted translation
https://lwn.net/Articles/849531/

针对 Linux 系统的若干种攻击都依赖于对 fork()系统调用进行暴力调用(brute-force),因此,一个名为 "Brute" 的 Linux 安全模块(LSM) 最近出现了,用来检测、挫败此类攻击。许多种攻击方式中都会重复进行 fork()调用,例如对 Stack Clash 漏洞或 Heartbleed 风格的漏洞的利用过程。最近 Brute patch set 第 6 版也已经公布出来,看起来它离 mainline 越来越近了。

自从 2020 年 9 月 John Wood 首次以 RFC 的形式发布这个 patch set 以来,它一直在演进中(当时过了几天 Kees Cook 重发出来的版本会更容易有个全面印象)。它最初被称为 "fork brute force attack mitigation "或 "fbfam",但这个名字在 Jann Horn 和 Cook 看来过于晦涩难明了。此外,有人建议把它变成一个 LSM。这两个建议在 10 月发布的第 2 版中都被采纳了。

但类似的想法其实更早的时候就有了。grsecurity 这组 kernel patch 中早就有 GRKERNSEC_BRUTE 功能,来防护那些对服务端程序使用暴力调用 fork()方式的攻击,以及对 setuid/setgid 可执行文件的利用。2014 年 Richard Weinberger 的一个 patch 也使用了类似的技术,如果 fork 出来的子程序因 fatal error(意味着它可能是正在进行的攻击中的一部分)而结束,就会对 fork()调用延后处理。这项工作没有进一步的进展了,所以 Cook 在内核自我保护项目(KSPP,kernel self-protection project)的 GitHub 仓库中添加了一个 issue,这也是 Wood 了解到这个想法来源。

在 documentation patch中,Wood 描述了 Brute LSM 会对哪些行为进行保护。基本来说,有几种攻击都是多次调用 fork()来得到攻击者所期望获得的 memory layout;每个被 fork 的子进程都可以通过各种方式探测系统信息,如果这些探测失败并导致子进程崩溃了,就会直接再 fork 另一个子进程来再次尝试。因为用 fork()创建的子进程与父进程共享相同的 memory layout,所以只要某一次能探测成功,就可以提供一些信息用来克服地址空间布局随机化(ASLR,address-space layout randomization),确定 stack canaries 的值(也就是栈中重要信息),或者用于其他恶意目的。

Brute 采用了与 grsecurity 或 Weinberger 的 patch 不相同的做法,当检测到问题时,它不会简单地推迟后续的 fork()调用。相反,Brute 会杀死所有与攻击相关的进程。此外,Brute 还能检测到其他种类的 fork()攻击,包括那些探测父进程而非子进程的攻击。它会只关注那些越过特权边界的进程中的崩溃,希望减少误报的数量。

它是根据崩溃率(rate of crashes)来决策的,而不是崩溃一次就判定为有问题。Brute 收集了依据 fork 关联起来、尚未用 execve()执行新程序的这些进程中发生的 "faults"数量。所有这些进程之间会共享一个 brute_stats 结构;执行一个新程序的话就会导致用一个新的 brute_stats 结构来跟踪新的 fork() 关系中的 faults(如果有的话)。

从一个进程被启动,到它或它的任何共享其内存布局的子进程(即没有调用过 execve())发生了 crash,或在多次 crash 之间的时段,这些时长会最终被用来判断是否发生攻击。但为了不至于太敏感,一旦发生 5 次 crash,就会计算出这段时间的指数移动平均线(EMA,exponential moving average)。EMA 会被用于判断是否发生了 "fast brute force" 这种类型的攻击。如果崩溃之间的周期 EMA 下降到 30 秒的阈值以下,就会触发对攻击的防护机制(attach mitigation)。对于 "slow brute force" 类型,会拿 fork 关系相关进程中的 crash 绝对数量与 200 这个阈值进行比较。可能今后会需要能有什么方式来配置这些值。

检测 crash 是利用了 task_fatal_signal() 这个 LSM hook,它是这组 patch set 中的第一个 patch 所加入的。每当内核向一个进程传递了 fatal signal 时,就会调用到这个 hook。Brute 还使用了已经存在的 task_alloc() hook 来检测 fork() 调用,以及 bprm_committing_creds() hook 来检测 execve()调用,还有 task_free() hook 来进行清理。

安全边界检查(security boundary check)是通过检测在执行新程序过程中用户和组 ID(real 、effective、saved、filesystem 等各种 ID)发生过哪些变化来实现的。patch 中没有提到 Linux capabilities,但实际上如果 capability 有改动,也会表明正在跨越特权边界,也许这是以后会增加的内容。除了 ID 变化之外,还用 socket_sock_rcv_skb()LSM hook 来检测是否使用了网络。总之是将 crash 检查只限制在那些执行 setuid/setgid 程序跨越特权边界、或者进行网络数据接收的进程上。这样做的目的就是为了减少误报的数量。

在随 patch 发出的 changelog 中可以看到,过去几个版本(为了方便大家作者还都提供了链接)所引发的需要注意的 review 意见不多。在写这篇文章的时候,最新一轮还没有得到任何评论。这似乎是一个对某些用户很有用的功能,如果在内核配置时没有打开,那么就不会给内核的其他部分带来什么影响。新增安全 hook 会在 fatal signal 发生的情况下被调用,这就是在这种情况下唯一的改变了。LSM 通常被看作是一个地方用来放置只有部分人想要的代码、并且其他人不想在他们自己的内核中受到影响。Brute 似乎很符合这种模式。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报