从DirtyPipe到Docker逃逸

渗透技巧 2年前 (2022) admin
921 0 0

编者注:本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。


背景

2022年2月23日, Linux内核发布漏洞补丁, 修复了内核5.8及之后版本存在的任意文件覆盖的漏洞(CVE-2022-0847), 该漏洞可导致普通用户本地提权至root特权, 因为与之前出现的DirtyCow(CVE-2016-5195)漏洞原理类似, 该漏洞被命名为DirtyPipe。  

漏洞利用公布后,便有很多安全从业者发表了详细的分析文章。  

Phithon 师傅在知识星球也发表了自己的一些想法以及复现过程中遇到的一个问题:利用该漏洞修改 Docker 内部文件时,其镜像也会发生改变。  

在之前的文章 《容器环境红队手法总结》中,也曾说到引起 Docker 逃逸的原因归为三类,一类是由于内核漏洞引起的,Dirty-Pipe也是内核漏洞,于是就开始了尝试由DirtyPipe到Docker逃逸的利用过程。


利用分析

Docker 逃逸,即从容器内部操作了外部的文件即可引发逃逸。此漏洞从内核态对文件进行修改,从 Docker 本身角度而言,由内核态修改的文件并不会被监控而去修改挂载的 fs,所以从而可以达到修改镜像的效果。  

从以上发现的问题延伸,从容器内部可以访问到 Docker 外部的文件,并对该文件进行修改,如果该文件会被执行,那就可以逃逸到宿主机。

从DirtyPipe到Docker逃逸

思考清楚利用链,现在需要做的就是找到这么一个文件。

Docker 运行后,其内部进程与外部进程有着 namespace 的区分,具体来说,Docker 通过外部命令对内部的操作,首先是由外部建立,再通过修改 namespace 来完成的。所以我想到的一个点是,在 namespace 修改完成后,还有没有资源属于宿主机,但是在容器内部可以访问到的。  

回忆 Docker 历史漏洞:

●CVE-2019-14271 由于 docker cp 命令引用了容器内部的 so 库引起的逃逸

●CVE-2019-5736 由 Docker 组件 runC 可被内部访问,可被替换引起的逃逸

以上两个漏洞都是因为外部资源由容器内部可控而引起的。  

我们去了解一下这两个漏洞是如何修复的:  

●官方修改了docker cp的流程,在 chroot 到容器之前,强制引用so库。

在介绍 CVE-2019-5736 的修复之前,我们先来看一下 CVE-2019-5736 漏洞的分析以及利用。对于这个三年前的老洞,已经有许多分析文章以及 POC。  

直接看 POC(https://github.com/Frichetten/CVE-2019-5736-PoC/blob/master/main.go):

从DirtyPipe到Docker逃逸

其主要流程可以描述为以下5步:

1.将容器内的/bin/sh程序覆盖为#!/proc/self/exe。

2.持续遍历容器内/proc目录,读取每一个/proc/[PID]/cmdline,对”runc”做字符串匹配,直到找到runc进程号。

3.以只读方式打开/proc/[runc-PID]/exe,拿到文件描述符fd。

4.持续尝试以写方式打开第3步中获得的只读fd(/proc/self/fd/[fd]),一开始总是返回失败,直到runc结束占用后写方式打开成功,立即通过该fd向宿主机上/usr/bin/runc(名字也可能是/usr/bin/docker-runc)写入攻击载荷。

5.runc 最后将执行用户通过docker exec指定的/bin/sh,它的内容在第1步中已经被替换成#!/proc/self/exe,因此实际上将执行宿主机上的runc,而runc也已经在第4步中被我们覆盖掉了。


对于 CVE-2019-5736 的修复,在这里详细地去介绍一下,因为这是 DirtyPipe 引发 Docker 逃逸的关键。  

搜索runcgit commit记录  (https://github.com/opencontainers/runc/commit/6635b4f0c6af3810594d2770f662f34ddc15b40d)。

从DirtyPipe到Docker逃逸

添加了ensure_cloned_binary的判断,从字面意思看是确保是复制的二进制文件,跟进这个函数。

从DirtyPipe到Docker逃逸

从字面意思看,会判断是否为自身的复制,如果不是,则对自身进行复制,然后运行复制文件,关闭当前文件,跟进看怎样进行的复制。

从DirtyPipe到Docker逃逸

调用了memfd_create函数,该函数意思为在内存中创建一个匿名文件,也就是说runC将自身复制为一个匿名文件,然后在容器中运行,从而保证了容器内部无法获取runC文件本身。

那此处的runC已经被修改为匿名文件了,还有没有什么方式去获取真正的runC呢? 

看到利用以及修复,这时候很多师傅应该已经可以想到,使用runC去执行/proc/self/exe时,其符号链接指向的就是runC本身。  

然而,由于 Linux Namespace 机制,即使指向runC本身,内部的修改也可以看作一次copy,并不会对外部文件产生影响。但是,DirtyPipe突破了这层壁垒。  

DirtyPipe 这个洞的利用过程与 Linux 管道和 splice(2) 系统调用的实现机制有关,其对文件的操作由内核来操作,对于runC漏洞的修复,由于容器内部namespace的转换,文件的符号链接即使指向外部,也无法对外部的文件产生影响,而从内核层的操作便跨越了namespace对文件的影响,从而可以对外部的runC进行覆盖。

分析完利用链,接下来去看如何去利用。

我们需要去做的是知道DirtyPipe这个洞的利用条件以及能做什么。  

漏洞发现者在自己发表的文章(https://dirtypipe.cm4all.com/) 中讲述得很清晰。  

1. 该漏洞是作用于缓存机制上的,所以缓存刷新即设备重启后被覆盖的文件会恢复。

2. 该漏洞只能覆盖一个可读文件。

3. 该漏洞不能跨页覆盖,作者原 POC 给出的 Page-Size 为 4096 字节。

4. 该漏洞不能在页头或者页尾开始覆盖,即无法覆盖第一个字符。

刚好,我们现在得到了一个符号链接指向外部的runC,由于 magic links 的特殊性(会直接调用专属的处理函数并返回对应文件的文件描述符),通过 DirtyPipe 跨越 namespace 的限制覆盖此符号链接,就可以对宿主机的runC进行覆盖了。相对于CVE-2019-5736的利用,DirtyPipe 的覆盖比较麻烦,因为无法覆盖第一个字符,所以需要去寻找函数地址,通过写 shellcode 的方式进行利用。不过这相对来说就比较轻松了。


参考内容

https://www.anquanke.com/post/id/270067

https://mp.weixin.qq.com/s/WaRECg79Nxx08iekakrlMA

https://www.secrss.com/articles/15269



请关注我们的公众号及Github (https://github.com/ZhuriLab),可与逐日实验室团队深入交流EXP研究。


另,逐日实验室招聘红队研究员岗位,要求如下:

1.有内网渗透经验,承接公司高水平红蓝对抗,攻防演练项目,包括但不限于国家hw,省级hw,地市hw,大集团攻防演练等

2.对红队手法和工具进行研究,研究新型攻击手法,自动化渗透中的部分过程

3.对公司及产品进行红队视角的安全能力评估

4.常见应用,中间件漏洞挖掘

5.应急响应,根据补丁包漏洞复现等

以上能力不要求完全具备,具备其中2-3项即可。

加分项:有公有云安全、云原生安全、工控安全研究经历。


从DirtyPipe到Docker逃逸
从DirtyPipe到Docker逃逸

默安科技安全研究院旗下团队,“逐日”寓意为追逐技术永不停歇,专注于安全攻防研究,包括漏洞挖掘、逆向工程、红蓝对抗、代码审计、产品赋能等方向。



原文始发于微信公众号(默安逐日实验室):从DirtyPipe到Docker逃逸

版权声明:admin 发表于 2022年3月17日 下午5:13。
转载请注明:从DirtyPipe到Docker逃逸 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...