Linux内核漏洞——CVE-2022-0185分析与思考
日期:2022年03月07日 阅:85
CVE-2022-0185是一个Linux内核中”Filesystem Context”中的一个堆溢出漏洞,攻击者可以利用该漏洞发起DDoS攻击,实现容器逃逸和提升至主机权限。该漏洞是在Google KCTF(基于Kubernetes的CTF)漏洞赏金计划中被Crusaders of Rust[1]团队的成员Jamie Hill-Daniel和William Liu发现[2]的,研究员因此获得了31337美元的奖励。NVD官网[3]最新数据显示,该漏洞CVSS3.x的评分为8.4。
据William所言,存在问题的代码于2019年3月在5.1-rc1版本中被引入Linux内核,直至2022年1月18日(5.16.2版本),官方才发布补丁修复该漏洞。然而要想成功利用CVE-2022-0185却并不容易,William在博客中用大量篇幅来讲述漏洞发现的过程和整个利用链的过程,感兴趣的读者可以阅读博客[4]。本文的目的之一是希望读者能够理解该漏洞的原理,作为云安全从业者,能够做好针对性的检测和防御工作。下面笔者将给出理解该漏洞所需的背景知识,然后对该漏洞进行分析,并给出相关缓解和修复方案,最后思考该漏洞的防御工作。
免责声明:本文中提到的漏洞利用代码和分析皆已在github仓库[5]和研究员博客中公开,仅供研究交流使用,请遵守《网络安全法》等相关法律法规,切勿将其用于未授权渗透测试。
背景知识
1、Filesystem Context
Filesystem Context是在创建Superblock的挂载和重新配置时使用的[6]。Superblock记录了一个文件系统的特征,包括它的大小、区块大小、空的和已填充的区块及其各自的计数、inode表的大小和位置、磁盘区块图和使用信息,以及区块组的大小。
2、Capabilities —— CAP_SYS_ADMIN
Capabilities机制是在Linux内核2.2版本之后引入的,它的出现是为了对root权限进行更细粒度的控制,实现按需授权。常见的capability所允许的操作或行为如下表所示[7]:
本文需要关注的是CAP_SYS_ADMIN,它提供众多命令的权限,如mount(2),umount(2),clone(2) 和 unshare(2)等。
3、Seccomp —— Docker与Kubernetes的区别
Seccomp 全称Secure computing mode,意为安全计算模式,自 2.6.12 版本以来一直是 Linux 内核的功能。它可以用来对进程的特权进行沙盒处理,从而限制了它可以从用户空间向内核进行的调用。只有当Docker在构建时使用了Seccomp,并且内核在配置时启用了CONFIG_SECCOMP,这个功能才可用。可以用以下命令来检查当前环境是否支持Seccomp:
grep CONFIG_SECCOMP= /boot/config-$(uname -r)
当使用Docker运行一个容器时,它会使用默认的配置文件[8],除非使用–security-opt参数来指定自定义配置文件。该配置文件是一个允许列表,它默认拒绝访问系统调用,只有列表中的系统调用可以执行,一些重要的系统调用如clone,ptrace,unshare等都默认禁止在Docker中执行,如图1所示:
图1 Docker容器中默认禁用unshare
被禁用的原因在官方文档[9]有所说明,感兴趣的可以阅读了解。
但在早先版本(1.22版本之前)的Kubernetes集群中使用Docker时,Seccomp机制却是默认禁用的。在Kubernetes集群中创建一个普通的Pod资源,检查Seccomp机制的状态和Pod内部系统调用的执行情况,如图2所示:
图2 Kubernetes集群中Pod内部默认不禁用unshare
可以看到Seccomp的状态值为0,代表禁用状态。
自1.22版本开始,Kubernetes引入了SeccompDefault特性,用来增强集群环境内的安全性。当该特性启用时,kubelet将默认使用由容器运行时定义的RuntimeDefault Seccomp配置文件,限制集群环境内的系统调用。
漏洞分析
1、漏洞成因
该漏洞发生Filesystem Context处理legacy参数时,由fs/fs_context.c的legacy_parse_param函数中存在的整数下溢引起,问题源码如下:
在第551行(源代码中所在行数,下同)存在一个边界检查,如果(len>PAGE_SIZE- 2 – size),将返回一个错误;但当size大小为4095或更大时,因为PAGE_SIZE是4Kb ,无符号减法PAGE_SIZE – 2 – size的计算结果将是一个巨大的正值,该正值大于len,所以检查将不会触发,然后就会有一个越界写入(在第566行):
因此通过向有漏洞的函数发送超过4095字节的数据,可以绕过输入长度检查,导致越界写入。这使得攻击者可以写到内存的其他部分,导致系统崩溃或运行任意代码从而实现容器逃逸或权限提升。
2、漏洞利用条件
该漏洞在宿主机上用于提升权限时,暂未发现利用的前提条件,只需以非root用户执行代码即可。
若用于容器逃逸,因为容器环境的安全隔离机制,需要判断容器内的环境是否满足一定条件。公开的利用链中包括特权系统调用,如fsopen(),因此需要攻击者拥有CAP_SYS_ADMIN capability(在任何命名空间),但该capability往往在容器以特权启动时被授予,或者添加–cap-add=SYS_ADMIN参数授予,并不会广泛出现。
然而,该capability可以通过unshare系统调用获得。unshare系统调用会将进程分配至新的namespace,如在容器内部使用unshare -U命令可以使用户进入一个新的user namespace,由于Linux capability继承的机制,新的namespace拥有全部的capabilities,也包括CAP_SYS_ADMIN。
通过上文的背景知识可以了解到比较矛盾的是,在Docker容器中,因为Seccomp机制的限制,unshare系统调用会被禁止,所以此种方法在普通业务容器中并不适用。但当处于低版本(1.22版本之前)的Kubernetes集群环境中,在默认配置情况下,非特权用户可以在Pod内部顺利执行unshare系统调用。因此,CVE-2022-0185用来容器逃逸的场景主要限于低版本Kubernetes集群环境。
漏洞利用
漏洞发现团队在GitHub仓库公开了漏洞利用代码,其中fuse版本是针对5.11.0-44内核版本的本地权限提升代码。它不会直接返回一个rootshell,而是使/bin/bash添加suid权限,该脚本利用思路大致如下:
1. 使用堆溢出来调整msg_msg的size,调用msgrcv()读内存,触发越界读取(注:调用msgrcv()读取内核数据时,带上MSG_COPY标志避免unlink时崩溃),接着使用open(“/proc/self/stat”, O_RDONLY)的技巧喷射许多seq_operations结构,尝试读取该结构泄露的指针,由此获得内核基址,并计算出modprobe_path地址:
2. 然后利用作者提出的利用msg_msg对象进行任意地址读和写技术[11][12],该技术需要用到userfaultfd技术,但从内核5.11版本开始,非特权的userfaultfd默认是禁用的,所以作者在此引入了FUSE技术来替代,用任意地址写来实现用自定义的脚本覆盖modprobe_path:
3. 最后使用execve触发modprobe,利用modprobe_path覆写技术,成功赋予/bin/bash suid权限:
脚本成功利用后可使用bash -p获得root权限。
要想使利用代码适用不同的内核版本,还需调整代码中single_start和modprobe_path的偏移量。
kctf版本代码可实现在GKE环境中完成容器逃逸,但是并不是100%可以成功,利用代码主要依赖FUSE和SYSVIPC弹性对象来实现任意写入。该版本代码若要适用不同的集群环境,需要修改的内容较多,本文暂不赘述。
除了”Crusaders of Rust “团队的利用代码,还有另一位研究人员发表了漏洞利用代码和技术分析文章[13],同样详细讲述了漏洞的利用过程,感兴趣的读者可以阅读。
补丁分析
查看官方针对此漏洞的补丁[14],修复后的代码如下:
修复方案较为简单,仅将之前的减法运算变更为加法,即条件判断改为size+ len + 2 > PAGE_SIZE,就可以解决这个问题。
漏洞修复与缓解
用户可以升级Linux kernel到5.16.2版本来修复该漏洞。但是该修复版本并不适用于所有Linux发行版,包括那些使用Linux kernel开发的系统。对于这些暂时没有可用补丁的系统,建议用户禁用非特权用户命名空间。
在Ubuntu系统中,可以使用以下命令来禁用非特权用户命名空间:
Red Hat 用户可以使用以下命令来禁用用户命名空间:
对于在Amazon EKS,Azure AKS和Google GKE环境的用户,可以通过更新节点镜像的方式修复漏洞[15]。
防范措施
了解漏洞的原理和利用条件之后,便可以从利用链的不同环节去防范此漏洞的利用。除了升级内核或更新补丁外,还可以用以下方法进行防范:
1. 在容器环境中启用Seccomp机制,确保unshare系统调用的禁用。
2. 对于低版本的Kubernetes环境,可以禁用非特权用户命名空间,具体参考上文中漏洞修复中步骤。
3. 对于1.22版以上的Kubernetes,可以在资源创建时使用SecurityContext添加默认的Seccomp或AppArmor配置文件,以保护任何Pod、Deployment、StatefulSet、Replicaset或Daemonset。使用运行时默认Seccomp配置限制Pod使用unshare系统调用,具体配置方法如下:
4. 谨慎部署privileged特权容器,谨慎给Pod添加CAP_SYS_ADMIN内核能力。CAP_SYS_ADMIN虽只是众多capabilities中的一种,但其代表的权限略高,据《CAP_SYS_ADMIN: the new root》[16]中介绍,许多开发者会在授权capability时不知道如何细分,最终选择CAP_SYS_ADMIN来满足环境。
总结与思考
Linux作为一款免费开源的操作系统,被越来越多的用户和企业使用。正因用户数量大,使用范围广,一旦其内核曝出相关漏洞,往往后果严重。观察近几年曝出的内核相关漏洞,大多数是问题代码存在已久,在和不同的技术融合时,才作为漏洞被研究者挖掘出来。CVE-2022-0185虽已公开利用代码,但因为其利用代码适用性的问题,预测用于“本地权限提升”的可能性要大于容器逃逸。即便如此,由于容器共享宿主机内核的缘故,集群环境中大多数宿主机为Linux系统,云原生环境安全问题仍不容小觑。
希望读者以此文章对该漏洞有更好的了解与认识,建立针对该漏洞的方法和检测机制,共同建设云环境安全。
(来源: 绿盟科技研究通讯)
参考文献
[1] https://cor.team/
[2] https://www.openwall.com/lists/oss-security/2022/01/18/7
[3] https://nvd.nist.gov/vuln/detail/CVE-2022-0185
[4] https://www.willsroot.io/2022/01/cve-2022-0185.html
[5] https://github.com/Crusaders-of-Rust/CVE-2022-0185
[6] https://www.kernel.org/doc/html/latest/filesystems/mount_api.html#the-filesystem-context
[7] https://man7.org/linux/man-pages/man7/capabilities.7.html
[8] https://github.com/moby/moby/blob/master/profiles/seccomp/default.json
[9] https://docs.docker.com/engine/security/seccomp/#significant-syscalls-blocked-by-the-default-profile
[10] https://www.openwall.com/lists/oss-security/2022/01/25/14
[11] https://www.willsroot.io/2021/08/corctf-2021-fire-of-salvation-writeup.html
[12] https://syst3mfailure.io/wall-of-perdition
[13] https://www.openwall.com/lists/oss-security/2022/01/25/14
[14] https://github.com/torvalds/linux/commit/722d94847de29310e8aa03fcbdb41fc92c521756
[15] https://jfrog.com/blog/the-impact-of-cve-2022-0185-linux-kernel-vulnerability-on-popular-kubernetes-engines/
[16] https://lwn.net/Articles/486306/