CVE-2024-21626 容器逃逸漏洞分析
2024-2-1 16:18:59 Author: bestwing.me(查看原文) 阅读量:34 收藏

TL; DR

最近公开了一个 runc 容器逃逸的公告, 公告细节链接为: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv

从公告看漏洞影响范围是: >=v1.0.0-rc93,<=1.1.11 , 补丁版本为: 1.1.12, 这里我的复现版本是:

1
2
3
4
5

$ docker info | grep "runc"
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
runc version: v1.1.7-0-g860f061

然后今天看到, 于是和 @explorer 和 @leommxj 简单看了一下。

漏洞分析

从公告讲就是 runc run 或者 runc exec 的过程中有存在没有及时关闭的 fd ,导致文件描述符泄漏在容器环境中,用户可以通过这个文件描述来进行容器逃逸。

首先来做一个赛博考古, 公告提到该漏洞是在 v1.0.0-rc93 这个版本引入的,在这个版本找到了两个打开 cgroup 地方。

一处是在https://github.com/opencontainers/runc/commit/fad92bbffa9c13652c07f1966606089e28442a87

(m *manager) Apply(pid int) (err error) 函数中加载了 cgroup , 然后在 func (p *initProcess) start() 函数里调用到了。具体文件行号为 fs.go:339

1
2
3
if err := p.manager.Apply(p.pid()); err != nil {
return newSystemErrorWithCause(err, "applying cgroup configuration for process")
}

image.png

此处在rc93 这个版本 release 的

另外一处是在 https://github.com/opencontainers/runc/commit/e0c0b0cf321252b8d964fc64d62d21f107615304 这个commit 中, 但是这个 commit 是 rc92 中 release的, 由于我和 @leoomxj 都暂时没看到这个 commit 打开的 cgroup 是否close 掉了,所以这里也提一句。

image.png

到这和仔细阅读公告,我们大概了解了就是打开 cgroup 文件的 fd 没有及时清理导致的泄漏。接着我们开始做一点简单复现

漏洞复现

公告中提到了如果设置 cwd 为 /proc/self/fd 就会导致逃逸

If the container was configured to have process.cwd set to /proc/self/fd/7/ (the actual fd can change depending on file opening order in runc), the resulting pid1 process will have a working directory in the host mount namespace and thus the spawned process can access the entire host filesystem.

attack 2 runc exec 过程 (docker exec)

那么做如下操作:

image-20240201211519027

这时候发现我们当前的 cwd 目录其实就是在 /sys/fs/cgroup 中,而且是容器外的 cwd, 于是我们使用多个 ../ 就能读取主机的文件系统文件。

image-20240201211835141

明显能看到 docker exec 的时候 /proc/self/fd/7 确实指向了 cgroup , 于是以此文章提出了一种逃逸场景

The same fd leak and lack of verification of the working directory in attack 1 also apply to runc exec. If a malicious process inside the container knows that some administrative process will call runc exec with the --cwd argument and a given path, in most cases they can replace that path with a symlink to /proc/self/fd/7/. Once the container process has executed the container binary, PR_SET_DUMPABLE protections no longer apply and the attacker can open /proc/$exec_pid/cwd to get access to the host filesystem.

runc exec defaults to a cwd of / (which cannot be replaced with a symlink), so this attack depends on the attacker getting a user (or some administrative process) to use --cwd and figuring out what path the target working directory is. Note that if the target working directory is a parent of the program binary being executed, the attacker might be unable to replace the path with a symlink (the execve will fail in most cases, unless the host filesystem layout specifically matches the container layout in specific ways and the attacker knows which binary the runc exec is executing).

具体场景为, 攻击者已经有了容器内shell, 然后需要主机外有 docker exec 命令, 且需要用到 cwd 参数, 然后攻击者得判断或者指定用户即将设置的 cwd 路径和当前这个 runc 是不是也是 fd 为 7 的时候指向 cgroup , 然后提前设置好符号链接指向 /proc/self/fd/7 , 复现流程如下:

假设我即将设置的 cwd 为 /tmp/hacker, 在容器中执行以下命令

  • ln -s /proc/self/fd/7 /tmp/hacker

然后容器外执行一下命令

  • docker exec -w /tmp/fuck -it cve-2024-21626 /bin/bash

此时就会发现cwd已经是外面的/sys/fs/cgroup 了

image-20240201212846550

attack 1 (docker build) -> 恶意镜像

这里也提一下 docker builid 镜像的攻击手段, 我们从 https://snyk.io/blog/cve-2024-21626-runc-process-cwd-container-breakout/ 这个博客可以看到受害者执行一个 run 镜像的操作就被容器逃逸了。

这里的我的 Dockerfile 内容如下:

1
2
3
FROM ubuntu:22.04
RUN ls -al ./
WORKDIR /proc/self/fd/8

首先 build 我的恶意镜像

  • docker build -t test .

然后执行恶意镜像

  • docker run --rm -it test bash

就会发现此时 cwd 就是在 cgroup, 通过 ../../ 就能穿越到 host 目录中

image-20240201213539584

补丁分析

从这个 2a4ed3e75b9e80d93d1836a9c4c1ebfa2b78870ecommit 中能看到几个比较明显的安全补丁(还有缓解措施)

  • 使用 O_CLOEXEC flag 来打开文件, 避免子进程继承了父进程的 fd

image.png

详情 commit 链接: https://github.com/opencontainers/runc/commit/89c93ddf289437d5c8558b37047c54af6a0edb48

  • 新增了 verifyCwd 函数来判断 cwd 是否存在来判断是不是在容器内

image.png

  • 新增 UnsafeCloseFrom 函数来关闭当前进程中大于或等于minFd的所有文件描述符,除了那些对Go运行时关键(例如netpoll管理描述符)

image.png

疑问

  1. 究竟是那个 commit 是真正引入漏洞的
  2. 为什么提到了 PR_SET_DUMPABLE 这个, 我印象中这个是 core dump 相关的, 在 runc 中这个起了什么作用?

文章来源: https://bestwing.me/CVE-2024-21626-container-escape.html
如有侵权请联系:admin#unsafe.sh