前言
试想在渗透过程中拿到了一个宿主机的权限,但权限过低无法读写文件,应该如何提权或者实现对该宿主机的其他目录进行任意读写操作呢?
Aleksa Sarai于2019年公布了一个docker symlink-race attack漏洞 - 在Docker 18.06.1-ce-rc2版本之前,docker中的FollowSymblinkInScope()
函数有可能因race condition(条件竞争)遭受**TOCTOU(Time of Check to Time of Use)**攻击,而docker cp的实现大量使用了该函数,因此docker cp存在被利用的危险。
前置知识
TOCTOU Attack
从字面意思上理解,TOCTOU(Time of Check to Time of Use) 实际上就是指check(检查)到use(使用)的这段时间。
Symlink
Symlink也就是软链接(symbolic link)、符号链接。
假设A是B的软链接,A指向的inode实际上与B指向的inode是不一样的,但是A的数据块中存放了B的路径(具体见操作系统中软链接和硬链接的概念与区别)。
linux中设置一个软链接(类似于Windows上的快捷方式):
ln -s /home/a/old symlink
FollowSymlinkInScope
在docker中,FollowSymlinkInScope
函数的作用是将一个path进行安全地解析,解析后再进行使用。
问题就在于 - 解析完之后和使用之前有间隙,用Aleksa的话说就是:
After the full path has been resolved, the resolved path is passed around a bit and then operated on a bit later.
这就意味着,如果在解析检查完路径之后、使用之前添加一个**symlink(软链接)**,最后实际操作的实际是symlink
指向的路径,而非其原本想要使用的解析的路径。
docker cp
docker cp
命令就使用了该函数。
docker cp /a ctn_id:/b
这段命令表示将宿主机的/a复制到容器下的/b。在这个复制的过程中,docker会解析/b
,如果/b
是容器内的一个符号链接,那么就会将其在容器内解析为路径,最后再进行复制。
这里想象一个攻击场景:先让
/b
是一个容器里的正常路径,在容器检查解析完/b
之后,赶在容器使用该解析后的path之前,将/b
指向一个恶意的symlink
。最后/b
这个symlink会在宿主机上进行解析,如果该符号链接/b
在容器内指向容器中的根目录/
,那么现在他会在宿主机上被解析为宿主机的根目录/
,而并不是在容器里被解析。因此这段复制命令,会变成将宿主机下的/a复制到宿主机下的/
目录,这就实现了任意写的功能。再想象一个攻击场景:
docker cp ctn_id:/b /a
命令将容器下的/b
复制到宿主机下的/a
,利用该漏洞,我们可以将/b
用symlink指向我们想要读取的任意文件,在宿主机上解析之后,就能将该文件复制到/a
上了。这就实现了任意读的功能。
漏洞利用场景
拿到低权限账户,但该账户可以使用docker cp命令。可以通过该漏洞读取任意文件,也可以通过修改/etc/shadow
文件来实现提权。
题外话:但其实这种利用场景并不特别实用,如果拿到的宿主机用户并非ROOT权限,却又能使用docker的命令,那么其实使用Docker运行一个特权容器会更为简单。
环境安装
使用Metarget靶场安装存在漏洞的docker版本
- Ubuntu 16.04 or 18.04
- Python >= 3.6 (Python 2.x is unsupported!)
- pip3
1 | #安装CVE-2018-15664环境 |
POC解析
我们下载好后的POC结构如下:
Dockerfile
1 | # Build the binary. |
该Dockerfile主要是为了构建漏洞利用程序symlink_swap(通过gcc来编译),并将其放在容器的根目录下。在该根目录下创建一个w00t_w00t_im_a_flag
文件,文件内容为"FAILED -- INSIDE CONTAINER PATH" >"
symlink_swap.c
1 |
|
symlink_swap这个恶意文件将会被放在docker容器里,被攻击者用来循环交换symlink和一个正常路径的名字。
如果我们想利用docker cp /shellcode ctr_id:/test
命令来完成向宿主机的根目录``/写入
shellcode`:
- 使用该恶意程序在容器中创建一个符号链接,该符号链接指向
/
根目录,命名该符号链接为evil
- 再创建一个
/test
的正常目录,无限循环地重命名(交换)/test
和/evil
的名字。 - 漏洞未触发时,docker守护进程解析的
/test
并不是一个符号链接,解析完之后,恶意程序将/test
和/evil
的名字进行交换(重命名) - 当docker开始copy操作的时候,实际copy的
ctr_id:/test
变成了一个符号链接(也就是重命名后的evil
) - 该符号链接在宿主机上被解析为
/
,而这个/
会指向宿主机的/
,从而我们完成了将shellcode
写入宿主机根目录/
的跨目录写操作。
run_read.sh和run_write.sh
POC中提供了两个shell可以利用:
run_read.sh
- 使用docker cp将容器内文件复制到宿主机
- 利用漏洞可以完成在宿主机上任意读的操作
run_write.sh
- 使用docker cp将宿主机上的文件复制到容器
- 利用漏洞可以完成在宿主机上任意写的操作
此处我们将使用run_write.sh来模拟任意写的操作
1 | echo "FAILED -- HOST FILE UNCHANGED" | sudo tee "$SYMSWAP_TARGET" |
结合恶意程序和该run_write.sh看,该shell的目的是将localpath文件的内容(“SUCCESS – HOST FILE CHANGED”)写入宿主机的/
中,从而实现在宿主机上跨目录任意写的操作。当然我们不能保证我们每次都是在docker守护进程解析之后才进行名字的交换,所以不是每一次都能成功。
漏洞复现
这里我们使用run_write.sh来完成攻击
直接./run_write.sh
运行即可
后续修复
https://developer.aliyun.com/article/704515
Reference
阿里云容器服务 ACK. (2019). CVE-2018-15664漏洞分析报告-阿里云开发者社区. [online] Available at: https://developer.aliyun.com/article/704515 [Accessed 24 Jan. 2022].
Sarai, A. (2019). oss-sec: CVE-2018-15664: docker (all versions) is vulnerable to a symlink-race attack. [online] Available at: https://seclists.org/oss-sec/2019/q2/131 [Accessed 23 Jan. 2022].
Appendix
源码:docker-ce-18.13.1-ce
docker/pkg/symlink/fs.go:FollowSymlinkInScope
1 | // FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an |