本篇文章仅用于技术研究和漏洞复现,切勿将文中涉及攻击手法用于非授权下渗透攻击行为,操作有风险,出现任何后果与本作者无关,谨慎操作!!!
重点看Tips提示
2019年2月11日,runC的维护团队报告了一个新发现的漏洞,SUSE Linux GmbH高级软件工程师Aleksa Sarai公布了影响Docker, containerd, Podman, CRI-O等默认运行时容器runc的严重漏洞CVE-2019-5736。漏洞会对IT运行环境带来威胁,漏洞利用会触发容器逃逸、影响整个容器主机的安全,最终导致运行在该主机上的其他容器被入侵。漏洞影响AWS, Google Cloud等主流云平台。日前,该容器逃逸漏洞的PoC利用代码已在GitHub上公布。这是CVE-2019-5736漏洞利用的Go语言实现。漏洞利用是通过覆写和执行主机系统runc二进制文件完成的。
漏洞点在于runC,runC是一个容器运行时,最初是作为Docker的一部分开发的,后来作为一个单独的开源工具和库被提取出来。作为 “低级别” 容器运行时,runC主要由 “高级别” 容器运行时(例如Docker)用于生成和运行容器,尽管它可以用作独立工具。像Docker这样的 “高级别” 容器运行时通常会实现镜像创建和管理等功能,并且可以使用runC来处理与运行容器相关的任务:创建容器、将进程附加到现有容器等。在Docker18.09.2之前的版本中使用的runC版本小于1.0-rc6,因此允许攻击者重写宿主机上的runC二进制文件,攻击者可以在宿主机上以root身份执行任意命令。
runC
runC是docker中最为核心的部分,容器的创建、运行、销毁等操作都是通过runC程序来完成的。我们来看一下runC文件,如下所示:
当我们运行docker run
等命令的时候实际上在底层调用的是runC程序,所以整个流程大概如下:
其中虚线位置表示,当runC生成一个子进程后runC程序将会结束占用。
当触发POC后,runC程序会被重写并执行:
/proc/
根据官方文档,/proc/
文件夹类似于一个文件系统,其中存放着各个进程与本地文件之间的映射,其中:
/proc/[PID]/exe: 一种特殊的软连接,是该进程自身对应的本地文件
/proc/[PID]/fd/: 这个目录下存放了该进程打开的所有文件描述符
/proc/self/: 不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/
/proc/[PID]/exe的特殊之处在于当权限通过的情况下打开这个文件,内核将会之间返回一个指向该文件的文件描述符,并非按照传统的打开方式做路径分析和文件查找,这就会导致绕过了mnt命名空间和chroot的限制。
当执行docker exec
命令的时候,runc启动并加入到容器的命名空间中去,其实这个时候,容器内的进程已经能够通过内部的/proc/观察到它,因此通过打开/proc/[runc-PID]/exe可以获取宿主机上的runc文件标识符,由此能够达到覆盖的能力。
POC地址:https://github.com/Frichetten/CVE-2019-5736-PoC
#!
在unix中,凡是被#!
注释的,统统是加载器的路径,常见的有#!/bin/sh
,表示将该注释后的代码交给/bin/sh
处理,我们常见的处理python脚本一般在bash中输入python run.py
,而如果在run.py
中将注释换成#!/path/to/python
,则在bash中执行run.py
即可。
在此poc中,作者将/bin/sh
进行了覆盖,修改成了#!/proc/self/exe
,意义在于当宿主机执行docker exec -it ID /bin/sh
时,/bin/sh
将会替换成执行调用者自己,也就是宿主机下的runc文件,此时将会执行runc文件。
两个for循环
在第一个for循环中,攻击者持续监测/proc/目录,当产生runc进程时,以可读的方式打开runc文件夹获取文件标识符。
在第二个for循环中,攻击者持续监听等待runc程序结束占用后就能够用之前循环获得的文件标识符以可写的方式向runc文件内写入payload。
宿主机利用攻击者提供的image来创建一个新的container,拥有container root权限,并且该container后续被docker exec attach。一句话描述,docker 18.09.2
之前的runC存在漏洞,攻击者可以修改runC的二进制文件导致提权。
18.09.2
1.0-rc6
下载地址:前往下载
添加更新源到 /etc/apt/source.list
文件中
Sudo apt-get update ##更新apt软件包索引
Sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common ##安装软件包,以允许apt通过HTTPS使用镜像仓库。
Curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add – ##添加Docker的官方GPG密钥
Sudo apt-key fingerprint 0EBFCD88 ## 验证密钥指纹是否为 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88
Sudo add-apt-repository “deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable” ##设置稳定存储库,直接写入到/etc/apt/source.list
文件中。
Sudo apt-get update ##再次更新apt软件包索引,可以看出新增docker镜像仓库。
到这里环境均已准备完毕!!!
Tips:建议提前拍好快照,漏洞复现完成之后可能会造成docker无法使用
卸载已经安装的docker
sudo apt-get remove docker docker-engine docker-ce docker.io
列出docker可用版本
apt-cache madison docker-ce
以下列表中选择低于18.09.2版本进行安装
Sudo apt-get install docker-ce=18.06.1-ce-3-0-ubuntu
启动docker并查看docker和runc版本(均在漏洞影响范围内)
下载CVE-2019-5736漏洞POC
git clone https://github.com/Frichetten/CVE-2019-5736-PoC.git
修改payload内容
此时编译payload需要go环境,直接安装即可,生成可执行脚本main
编译完成后,我们运行一个漏洞环境(以CVE-2020-1957漏洞为例)
这里需要注意一下,安装完docker-ce之后,docker-compose是默认没有的,直接使用apt-get install docker-compose 或 pip install docker-compose命令可能会出现错误(尝试安装),解决方法就是下载一个docker-compose来进行安装,命令如下:
curl -L https://get.daocloud.io/docker/compose/releases/download/v2.6.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose //下载docker-compose 版本为2.6.1 并添加到 /usr/local/bin目录下
sudo chmod +x /usr/local/bin/docker-compose //赋予docker-compose执行权限
docker-compose -v //查看版本
##以上命令需要root权限执行!!!
启动环境
执行以下命令将生成的main脚本cp到docker容器中(这就是模拟攻击者获取了docker容器权限,在容器中上传payload进行docker逃逸)
docker cp /home/szg/CVE-2019-5736-PoC/main 3d5341ae0bf5:/home
执行如下命令,进入容器,查看脚本是否拷进容器并启动main脚本
docker exec -it 3d5341ae0bf5 /bin/sh (第一次需使用/bin/sh启动)
KALI启动监听
ubuntu启动一个新终端,执行如下命令再次进入容器,触发payload,成功反弹shell,此时权限为服务器权限,docker逃逸成功。
更新容器至 18.09.2
版本以上。