今天是我与@bluec0re一起研究CVE-2020-14381这个内核漏洞的周年纪念日,所以,我专门撰写本文来纪念一下。实际上,这个漏洞本身是由Project Zero的Jann Horn发现的。虽然我掌握了利用这个漏洞所需的大部分元素,但由于漏洞利用本身并不是特别令人兴奋,所以这里就不多做介绍了。老实说,这个漏洞最让我感兴趣的地方,是其生命周期,尤其是补丁程序在不同发行版上应用的不均衡性。同时,本文也将简单介绍相关的硬件侧信道技术,因为这是我第一次使用该技术。
漏洞概述
关于这个漏洞的描述,网络上面已经随处可见了,但这里我还是要再总结一下。futex syscall的主要参数是一个用户空间中的地址,这个地址可能属于一个文件支持的映射。在这种情况下,futex的内核对象key将保存并维护对inode对象的引用,但没有保存对文件的挂载点的引用。如果挂载点消失了,它的相关内核结构体就会被释放,但inode不会。这会导致一个安全漏洞,因为inode本身含有指向这些结构体的字段,比如其super_block结构体。
因此,futex代码路径对inode的进一步使用可能会触发使用后释放漏洞。对于该漏洞,Jann所强调一个特殊的代码路径发生在futex被销毁的时候:对inode的最后一个引用被释放,同时,该inode也需要被释放。这是在iput中完成的,之后,将调用iput_final。iput_final及其子调用将调用存储在super_operations结构体中的inode管理函数;该结构体是通过super_block对象访问的。第一个实例出现在iput_final的开头,之后会调用drop_inode函数。
要想利用这个安全漏洞,需要满足下列条件:
· 成功卸载挂载点。这在几年前是不可能的,但现在随着非特权用户命名空间的规范化,这已经成为可能。这是个很好的例子,说明这个功能从来都不是一个微不足道的安全权衡(无特权沙箱与增强的内核攻击面之间的权衡),这反过来又让人有点惊讶,因为所有主流发行版都默认启用了它们,但并没有引起太多争论。
· 确保op->drop_inode()函数能够成功执行(非SMEP或KASLR绕过技术)。
· 在之前的op->drop_inode间接访问能够成功执行(非SMAP或堆栈/堆泄漏)。
· 在一次调用中完成所有的事情,因为在inode状态不正确、super_block已损坏以及某些链接列表解链接的情况下,在剩余的iput_final中,我们甚至能否走到第二个super_operations函数指针调用(evict_inode),都是值得怀疑的。
漏洞利用方法
我想到的第一个漏洞利用方法如下所示:
· 等待super_block被释放。这个操作是在RCU回调函数中完成的,所以,需要通过某种方式等待umount返回后RCU宽限期的结束,例如用membarrier。对于PoC来说,在加速的宽限期内喷洒allocs就行了,因为super_block slab与kmalloc-2k并不是超级忙。
· 通过动态堆分配原语(例如sendmsg辅助数据)覆盖释放的super_block。
· 令s_op指向一个由攻击者控制的缓冲区。
· 令drop_inode指向gadget链,这些gadget可以通过堆栈跳转到super_block或super_operations缓冲区(这两个缓冲区都必须在寄存器中,而且几乎完全受控)。在这种情况下,常见的gadget的例子是push reg; jmp/call [reg+x],然后,可以用pop rsp; ret将gadget链放入[reg+x]。
· 用无约束ROP执行所需操作,修复堆栈,然后返回。
由于这种漏洞利用方式依赖于对内核映像的精确了解,所以维护起来将非常让人头疼;但对于一个没有内核空间读原语的原始函数指针执行来说,这已经是最好的方式了。像这样的漏洞利用的可移植性问题本身就是SMEP的一大优势:尽管它未必能够防御这种攻击,但却使许多攻击者对武器化该漏洞的吸引力大大降低。
SMEP机制的面世时间,虽然只比SMAP晚了2年,但它已经非常普及了。另外,如果您的漏洞利用方式确实依赖于非SMEP环境,但目标还可以启用软件SMEP,这种情况在运行时是难以分辨的,所以这种情况非常棘手的。不过,非SMAP环境的先决条件倒是问题不大。随便举个例子,AWS EC2的CPU名册上仍显示了许多不支持SMAP的CPU。
关于信息泄露漏洞
无论如何,要想利用这个漏洞,至少需要借助于一个信息泄露漏洞。最重要的是,我们必须获取gadget的内核基地址,然后,我们才能通过堆泄漏或类似的方法来攻击支持SMAP功能的CPU(实现上面提到的第3个先决条件,即在内核空间中拥有“攻击者控制的缓冲区”)。实际上,通过堆/栈泄漏通常也可以获得.text地址,所以,这实际上可谓一举两得。但是,并不是所有情况下都能获得合适的信息泄漏漏洞,这与常见的反KASLR的说法相反。而且,即找到了一个信息泄露漏洞,也不意味着它能为当前的漏洞利用带来帮助。
例如,CVE-2020-10732是在去年大约同一时间发布有一个很好的信息漏洞候选者,该漏洞位于Linux kernel的用户空间核心转储的实现中。本地攻击者可利用该漏洞造成程序崩溃,获取敏感的内核数据。但是,在缺乏现成的概念验证代码的情况下,就要求我们自己深入理解coredump的生成代码,以找到可以获取.text段的一个对象,并推导出处于我们的控制之下的堆地址。简而言之,这项工作的工作量还是很大的。同时,在同一个exploit中利用两个安全漏洞时,我们还必须考虑这两个漏洞的各种限制条件——对于我们正在考察的主要漏洞来说,非特权用户的命名空间,要想用于coredump,必须能够检索核心文件,换句话说,不能运行在容器中。幸运的是,对于我们的项目来说,其攻击目标是非SMAP容器,所以,我们就能够避免在一个信息泄露漏洞上花费九牛二虎之力后却发现它是毫无价值的尴尬局面。但如果我们的目标是SMAP容器,那就只能这样了,因为过多的工作会超出我们这个项目的资源预算。
硬件侧信道漏洞
然而,对于内核的.text来说,情况就不一样了,因为早就有通用的、公开记录的方式来获取内核基地址:硬件漏洞。我个人从来没有利用过任何这类漏洞,甚至认为它们是一种小众的漏洞利用技术,依靠的是不透明的CPU启发式方法,因此无法适用于所有的模型——也就是说,我认为这种漏洞缺乏广泛的适用性。实际上,我这种想法是大错特错的,但幸运的是,我有机会接触到许多专家(@tehjh,@_fel1x,@_tsuro),我们可以从他们那里了解真实的情况。
虽然允许跨安全边界泄露内存的侧通道漏洞有望得到缓解,但还有很多泄露地址的侧信道漏洞,例如Spectre漏洞。这些漏洞可能还会存在很长一段时间。对于这个项目,我使用了2016年Spectre之前发布的Jump Over ASLR技术。它很容易理解(尤其是本人可以请教上述专家),而且还有一些公开的PoC,我们只需根据自己的场景进行相应的调整,即可用于自己的项目。Jump Over ASLR基于分支目标缓冲区的内部工作机制,其中用户和内核分支可能会发生冲突。当这种情况发生时,CPU需要完成大量的工作,这些是可以观察到的。攻击者只要随意触发短内核路径并观察命中分支的偏移量,就能泄漏内核的基地址:然后,他们可以利用KASLR的低熵来尝试所有可能的基地址,并找到命中分支的基地址。
对于参数(要测量的分支),我们可以随意选择。实际上,我只使用导致快速返回用户空间的参数对creat syscall进行尝试,然后测量sys_creat和do_sys_open偏移量是否被击中。虽然偏移量必须相当精确,但不能精确到字节,因为在分支预测器中似乎使用了一些别名。我最初使用__fentry__作为额外的分支目标,两个符号的偏移量都是+5,我不知道将来效果会怎么样,但到目前为止这种方法仍然是有效的。
通过对假阴性和假阳性的适当过滤(基本上是对每个地址进行双重检查),这种方法就能轻而易举地搞定英特尔的新型CPU。在可预见的将来,这种方法应该仍然奏效。因此,至少对于已知的内核映像来说,我们基本上回到了KASLR之前的时代——请记住,我对于这种技术相对陌生,所以,仍然可能存在不为我所知的、更好的侧信道技术。
关于漏洞时间线的迷思
好吧,这是我个人觉得非常有趣的地方,因为我之前从来没有研究过内核漏洞的时间线。这个漏洞最初是在2020年2月28日被报告的,并于3月3日得到修复。因此,任何关注内核补丁的人都可以看到这个漏洞——即使你没有在上面花费太多时间;一般来说,由Jann Horn提交的漏洞都值得深入研究一番。需要注意的是,主要的内核分支都是在3月25日或4月2日修复该漏洞的。你可能会非常惊讶:“哦,哇,这几乎是一个月的时间”——先不要激动,且听我仔细道来。
首先,某些发行版几乎马上就应用了这个补丁:
· Arch Linux: 3月25日
· Gentoo: 3月25日
· Fedora: 3月26日
我知道它们不是专门针对工作站的,但是除了个人服务器之外,我从未见过它们被用在其他地方。实际上,第二批修复了这个漏洞的发行版则更适合服务器:
· Ubuntu 18.04 LTS: 4月7日
· Ubuntu 16.04 LTS: 4月24日
· Debian Buster (stable): 4月27日
· Debian Stretch (oldstable): 7月6日
如上所示,Debian确实有点落后,但总的来说,这是在补丁发布后的一个月内完成的,考虑到需要额外的处理来确保更好的性能和稳定性,这听起来还是合理的。至于Oldstable版本,好吧,毕竟是老版本了——只是有趣的是,在这个漏洞的处理上,Ubuntu的oldstable版本反而做的要更好一些。当然这意味着,知道该漏洞的攻击者有5到8周的时间在Ubuntu/Debian稳定版上利用这个漏洞。
5月7日,Project Zero才真正公开了这个漏洞的细节——从漏洞初次提交算起,这已经接近半年时间了:
· openSUSE: 10月11日
· RHEL 8: 11月4日
· Oracle Linux 8: 11月10日
· CentOS 8: 截止11月19日,仍未修复该漏洞。
所以,攻击者在该漏洞被公开披露后,有5个月的时间来利用suse系统中的这个漏洞,有6到7个月的时间来利用redhat及其衍生系统中的这个漏洞!考虑到有些攻击者可能提前2个月就注意到了这个漏洞,所以,他们分别分别有7个月和8-9个月来利用这两个系统。当然,这还是基于服务器在发布补丁就立即更新并重启这一假设的——实际上,很少有管理员能够做到这一点。
让人揪心的是,Linux内核每个月都会收到数以百计的漏洞报告——没有人知道其中有多少理论上具有可利用性的安全漏洞得到了修复。在我看来,这说明了一个主要问题:从安全的角度来看,基于cherry-picking策略整合补丁的内核分叉是注定要失败的,因为维护者根本无法对提交的补丁进行适当的分类,以适当地标记和应用到所有潜在的安全漏洞上面。本文介绍的这个漏洞就是证明该流程失效的一个很好的例子。但是,所有面向企业的内核似乎都是这样维护的。
从攻击性的角度来看,考虑到一些公司很少打补丁后立即重启机器,因此,手头上准备几个老旧的内核漏洞还是非常有用了。但这里真正给人以启发的是,即使有人做的一切都很正确,在补丁发布之前,仍有8个月的时间暴露在危险之中。这意味着,仅仅依靠N-day漏洞来“打天下”也是一个可行的策略——尤其是当你专注于那些很少有人谈论的漏洞时。
最后,我认为,我的下一篇文章可能要到4年后才能与您见面。
本文翻译自:https://blog.frizn.fr/linux-kernel/cve-2020-14381如若转载,请注明原文地址: