一. 前言
本文翻译整理自Quarkslab实验室[1],主要追溯了三个与Kubernetes相关的漏洞:CVE-2017-1002101、CVE-2021-30465和CVE-2021-25741,分别介绍了这些漏洞的原理以及对应的修复措施,并阐述了它们之间的关联——均与HostPath有关。在介绍漏洞之前,我们需要先了解一下Kubernetes相关的背景知识。
二. 背景知识:Volume—>HostPath
在默认情况下,容器中的数据都是非持久化的,当容器关闭或者重新启动时,容器中的文件将会丢失,为了解决这一麻烦,Kubernetes引入了Volume的定义。Volume作为Pod的一个属性,与Pod具有相同的生命周期,即使容器重启,Volume中的原有数据也将会保留,如此可以解决数据的持久性问题;其次,因为Volume可以被Pod中的任意容器使用,这样为多个容器之间的数据共享提供了便利。
Volume的类型多种多样,常见的有emptyDir、HostPath、configMip等,更多的类型可以参考官方文档[2]。本文重点关注的是HostPath类型的Volume,官方对其描述大致如下:HostPath类型的Volume会将宿主机上的文件或目录挂载到Pod中,这不是大多数Pod需要的东西,但一些特定的工作负载仍有访问节点资源的需求,这也是Kubernetes引入HostPath的原因。
然而,使用HostPath时也存在一定的风险,官方文档中就有以下相关警告:
Warning: HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible
原因是,站在安全的角度来看,HostPath的性质正在破坏容器所承诺的隔离特性:文件系统的隔离。在理想情况下,Pod不应该依赖宿主机的文件系统或配置,不管在它们在集群中的调度情况如何,运行方式应该是一样的。但HostPath是将宿主机上的文件挂载至容器内,依赖宿主机的文件系统,如果配置不当,可能会发生官方文档中举例的风险,如暴露宿主机上特权的集群凭证(如Kubelet)或特权的API(容器运行时套接字)等,导致攻击者可以在容器内进行逃逸攻击或权限提升。
本文暂不讨论因为HostPath配置不当引发的风险,主要探究与HostPath相关的Kubernetes漏洞本文暂不讨论因为潜在的配置不当HostPath引发的风险,希望能探究这一功能点如何使Kubernetes受到一些漏洞的影响。
三. 统计结果
我们从最新的CVE-2021-25741开始调查,发现了两个相关漏洞,一个是2017年底的CVE-2017-1002101,也与Kubelet有关,另一个是2020年底的CVE-2021-30465,与runC有关。而其中CVE-2021-25741是因为CVE-2021-30465而被发现的,它同时也是CVE-2017-1002101的不完整补丁的结果。
3.1
Kubelet CVE-2017-1002101
该漏洞是2018年披露的最严重的Kubernetes漏洞之一。它涉及运行在每个节点上、与API Server进行通信的Kubelet组件。攻击者通过符号链接竞争,有可能在没有实际授权的情况下将任意的HostPath挂载到Pod的容器中。更具体地说,是因为HostPath中的subpath功能是用户可以操纵的入口,它将由Kubelet在容器创建过程中读取和装载。此漏洞正是利用这样的特性:Kubelet在宿主机上是以root身份运行的,而符号链接是相对于“读者”(读取符号链接的用户)解析的。(注:也存在以低权限用户运行Kubelet的情况,被称为rootless Kubernetes,若如此,便可消除此类问题)
下面是一个简化的例子,由Brad Geesaman编写,在/rootfs/host挂载宿主机文件系统。可以在他的项目仓库中找到其他完整的例子[4]。
apiVersion: v1
kind: Pod
metadata:
name: subpath
spec:
containers:
- image: nginx:latest
name: setup
imagePullPolicy:
"Always"
command: ["/bin/bash"]
args: ["-c", "cd
/rootfs && rm -rf hostetc && ln -s / /rootfs/host &&
touch /status/done && sleep infinity"]
volumeMounts:
- mountPath: /rootfs
name: escape-volume
- mountPath: /status
name: status-volume
- image: nginx:latest
name: exploit
imagePullPolicy:
"Always"
command: ["/bin/bash"]
args: ["-c", "if
[[ -f /status/done ]];then sleep infinity; else sleep 1; fi"]
volumeMounts:
- mountPath: /rootfs
name: escape-volume
subPath: host
- mountPath: /status
name: status-volume
volumes:
- name: escape-volume
emptyDir: {}
- name: status-volume
emptyDir: {}
关于这个漏洞的更多详情可以参考《逃逸风云再起:从CVE-2017-1002101到CVE-2021-25741》。
最终解决这个漏洞的方案如下:
1. 在宿主机上对所有的subPath解析符号链接;
2. 对解析后的路径,从Volume的根路径开始,使用openat()系统调用依次打开每一个路径段(即路径被分割符/分开的各部分),在这个过程中禁用符号链接。对于每个段,确保当前路径位于卷内部;
3. 将/proc/<Kubeletpid>/fd/<final fd>绑定挂载到Kubelet的Pod目录下的一个子目录。该文件是指向打开文件的链接(文件描述符)。如果源文件在被Kubelet打开的时候被替换,那么链接依然指向原始文件;
4. 关闭文件描述符fd,将绑定挂载传递给Runtime。
使用文件描述符来避免使用路径时可能出现的竞争条件漏洞是一种常见的解决方案。具体可参考2006年出版的《The Art of Software Security Assessment》[5]一书中找到这方面的详细信息,过去的一些竞争条件漏洞也是用这种方案解决的。
3.2
runC CVE-2021-30465
2020年底,Etienne Champetier在runC中发现了一个新的漏洞,这也是一个符号链接竞争,利用了TOCTOU(time-of-check to time-of-use)。简单来说,TOCTOU指的是程序对某对象的安全检查步骤和使用该对象的步骤之间存在间隙,攻击者可以先构造并放置一个能够通过安全检查的合法对象,顺利通过目标程序的安全检查流程,然后立即使用恶意对象替换之前的合法对象。这样一来,目标程序真正使用的实际上是被替换后的恶意对象。值得注意的是,这个漏洞的发现得益于runC代码中的一个注释。Etienne发布了一篇详实的博客文章[6],介绍了该漏洞的原理以及如何在Kubernetes的环境下利用该漏洞。Kubelet守护程序使用的是容器运行时,而容器运行时在大多数情况下使用的是runC。如果想了解更多信息,也可以参考SIG-Honk的笔记[7]。
这个漏洞导致的后果与前一个漏洞相同,但利用起来相对更复杂,因为它涉及一个竞争条件。其原理是,runC调用一个名为securejoin. SecureJoinVFS()的函数来解析符号链接,然后再调用mount(),这个过程中会发生问题。作为Kubernetes用户,我们可以完全控制挂载的目标,所以我们可以操作这两个语句之间的值,以迫使挂载遵循指定的符号链接。
此漏洞的补丁[8]与之前的CVE-2021-30465漏洞补丁类似,包括使用进程的文件描述符,作为验证路径后的 "权宜之计"。
3.3
Kubelet CVE 2021-25741
CVE-2021-30465的披露激起了Google工程师的好奇心,他们直接在Kubelet二进制中发现了几乎相同的漏洞,即CVE-2021-25741。与前两个漏洞相同的是,在解析路径和实际挂载之间存在一个时间窗口,可以被攻击者利用。
有趣之处在于,它是由前文中的CVE-2017-1002101的不完整的补丁引起的。在之前的解决方案中,确实使用文件描述符来解决符号链接问题,但是新的问题出现在Kubelet的doMount函数中,该函数调用的实际是系统上的mount工具而不是mount系统调用。根据Linux手册[9],mount工具默认情况下是解析符号链接的。而攻击者可以在mount工具解析符号链接后和执行挂载操作前制造竞争条件攻击,从而绕过前述补丁的防御措施,POC可参考[10]。
该漏洞的修复方案较为简单,在调用mount工具时传递--no-canonicalize参数,便不再解析符号链接。
四. 总结与思考
容器在默认情况下拥有自己的文件系统,并通过各种机制进行隔离。Kubernetes的Volume功能允许将不同的文件系统附加到容器的现有文件系统中。但是,允许用户在mount的参数中输入任意的文件系统结构,特别是符号链接,是极其危险的行为,已经导致产生了各种漏洞。上文提到的三个漏洞也显示了漏洞修复工作的困难以及一个漏洞的披露可能导致类似或相关软件的其他漏洞披露。下面梳理了与这三个漏洞相关的主要事件和资源的简要时间线:
DATE | CVE | DESCRIPTION |
---|---|---|
2018年3月 | 2017-1002101 | CVE-2017-1002101 在Github的issue #60813中披露。 |
2018年3月 | 2017-1002101 | Twistlock撰写的关于迄今为止最严重的Kubernetes漏洞的深挖文章和Brad Geesaman撰写的POC文章已经发表。 |
2018年4月 | 2017-1002101 | Kubernetes博客发布文章.Fixing the Subpath Volume Vulnerability in Kubernetes。 |
2018年12月 | 2017-1002101 | Michelle Au and Jan Šafránek在2018年KubeCon NA会议上.How Symlinks Pwned Kubernetes (And How We Fixed It) 。 |
2020年12月 | 2021-30465 | Etienne Champetier 发现了runC漏洞CVE-2021-30465。 |
2021年3月 | 2021-30465 | Github and OSS-Security上披露了漏洞, Etienne Champetier发表了文章和POC write-up and a POC。 |
2021年9月 | 2021-25741 | CVE-2021-25741 在Github issue #104980上由Google工程师Fabricio Voznika和Mark Wolters披露 |
2021年10月 | 2021-30465 | Sig security Ian Coldwater、Brad Geesaman、Rory McCune和Duffie Cooley在2021年KubeCon NA上发表并展示了他们的POC。 |
2022年1月 | 2021-25741 | Google在其安全博客上发布了《探索容器安全:存储漏洞深究》。 |
2022年1月 | 2021-25741 | Arkadiy Litvinenko发表了 POC。 |
研究员在调研CVE-2021-25741遇到了符号链接和绑定挂载的结合,觉得十分有趣,因此希望通过一个简化的例子来说明两者结合时会发生的问题。[10]
见图1,灰色方框是目录,黄色是符号链接:
图1 符号链接示例[10]
如果你想做实验,请在一个单独的虚拟机中进行以下命令创建文件系统结构:
mkdir -p base/dir1 base/dir2 && ln -s ../../base base/dir1/loop
&& ln -s / base/dir2/root
如果你在base文件夹中运行以下mount(8)命令,会发生什么?
mount -o bind base/dir2 base/dir1/loop
通过观察可以发现,该命令效果与以下相同:
mount -o bind base/dir2 base
结果是dir2被绑定至base上,覆盖了文件系统中base下的所有文件,从而证明将一个子文件夹挂载到其父文件夹上是完全有效的,如图2所示:
图2 符号链接示例[10]
希望读者能够在程序设计与使用Kubernetes时谨慎使用符号链接、谨慎使用HostPath,全面考虑可能存在的风险,保证云原生环境的安全。
参考文献
[1] https://blog.quarkslab.com/kubernetes-and-hostpath-a-love-hate-relationship.html
[2] https://kubernetes.io/docs/concepts/storage/volumes/
[3] https://kubernetes.io/docs/concepts/security/pod-security-policy/#volumes-and-file-systems
[4] https://github.com/bgeesaman/subpath-exploit
[5] https://flylib.com/books/en/2.294.1.83/1/
[6] http://blog.champtar.fr/runc-symlink-CVE-2021-30465/
[7] https://hackmd.io/jAnCrvWEQ-u3JBDtf1N8TA#more-hacks
[8] https://github.com/opencontainers/runc/commit/0ca91f44f1664da834bc61115a849b56d22f595f
[9] https://man7.org/linux/man-pages/man8/mount.8.html
[10] https://github.com/Betep0k/CVE-2021-25741
内容编辑:创新研究院 李来冰
责任编辑:创新研究院 董炳佑
本公众号原创文章仅代表作者观点,不代表绿盟科技立场。所有原创内容版权均属绿盟科技研究通讯。未经授权,严禁任何媒体以及微信公众号复制、转载、摘编或以其他方式使用,转载须注明来自绿盟科技研究通讯并附上本文链接。