
一、漏洞简介
我们在执行功能类似于docker exec命令时,底层实际上是容器运行时在操作,例如runc。它的最终效果是在容器内执行用户指定的程序。
执行过程大致是这样的:runc启动,加入到容器命名空间,接着以自身(/proc/self/exe)为范本启动一个子进程,最后通过exec系统调用执行用户指定的二进制程序。
那么假设这样一种情况:在runc exec加入到容器的命名空间后,容器内进程已经能够通过内部/proc观察到它,此时如果打开/proc/[runc-PID]/exe去写入一些内容,就能够实现将宿主机上的runc二进制程序覆盖掉。这样一来,下次用户调用runc去执行命令时,实际执行的是攻击者写入的命令。
利用条件:docker version <18.09.2 runc version <=1.0-rc6
参考:容器逃逸成真:从CTF解题到CVE-2019-5736漏洞挖掘分析
二、漏洞复现
环境搭建
方法一:metarget一键部署
方法二:手动搭建
安装特定版本docker
安装特定版本runc
POC
参考:https://github.com/Frichetten/CVE-2019-5736-PoC/blob/master/main.go
代码第20行填入待执行的命令:
编译poc
复现步骤
因为该漏洞会破坏runc,所以先备份:
起一个容器并将poc复制到容器内
进入容器,执行poc
poc程序进入等待状态,这时我们另开一个终端窗口,在host上通过docker exec执行容器上的/bin/sh
POC执行成功:/tmp目录下存在我们创建的文件夹,即命令成功执行:
三、对poc的一些测试
测试一
容器运行poc,在宿主机上运行docker exec ** ls /
poc执行成功,但这时payload被写到哪里了呢?
容器中执行cat /bin/ls
可见payload被写进了容器的/bin/ls
二进制程序中,现在去容器中执行ls
就等于执行payload
测试二
在抓取pid这里,我们输出一下看看,是什么匹配到了runc
(即看一下此时的/proc/pid/cmdline
)
先试一下正常的复现流程:
再重新试一下宿主机运行docker exec ** ls /
可见,匹配到runc的/proc/pid/cmdline一直都是docker-runcinit
测试总结
由测试一可知,用docker exec执行容器里的任意命令,该命令的二进制程序就会被payload覆盖掉。
由测试二可知,抓取pid那里,docker exec执行任意命令,其/proc/pid/cmdline都是"docker-runcinit",因为docker exec就是容器运行时(runc)在操作。
所以,POC执行时,
(1) 我们先把容器内的/bin/sh的二进制程序覆盖为#!/proc/self/exe
(2)执行docker exec ** /bin/sh ,由测试一的结论可知/bin/sh的二进制程序(即#!/proc/self/exe)会被payload覆盖掉。那这个/proc/self/exe指向谁?由测试二的结论可知docker exec是runc在操作,所以self就是runc。故这里最终实现了payload覆盖runc。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)