runc 容器逃逸原理
1. 竞争条件
这是 runc 容器逃逸中一种经典的利用方式。以 CVE-2019-5736 为例,其核心原理是:
- 进程切换和文件句柄劫持:当我们在宿主机上执行
docker exec
等命令时,实际上 runc 会在容器内启动一个新的进程。在 runc 启动这个新进程到真正执行用户指定命令的这段极短的时间内,存在一个“窗口期” - 恶意代码的快速覆盖:攻击者可以在容器内通过一个精心设计的恶意程序,持续地监控并尝试以写权限打开 runc 进程的文件句柄(
/proc/self/exe
)。一旦 runc 进程完成了权限降级,文件句柄被释放但尚未关闭,攻击者的恶意程序就会立即抢占这个句柄,并向宿主机上的 runc 二进制文件写入恶意 payload - 获得宿主机 root 权限:当 runc 尝试执行后续命令时,它执行的不再是正常的二进制文件,而是已经被篡改的恶意代码。因为 runc 本身是以 root 权限在宿主机上运行的,所以攻击者就成功地以 root 权限执行了任意命令,实现了容器逃逸
2. 特权模式与危险配置
虽然这不是 runc 自身的漏洞,但它是最常见的容器逃逸方式之一,常常与 runc 的使用有关
- 特权容器(Privileged Container):如果一个容器被以特权模式启动(
docker run --privileged
),它将获得几乎所有宿主机的 root 能力。这种模式下,容器内的进程可以访问宿主机上的所有设备、挂载宿主机的文件系统,甚至可以操纵内核模块。攻击者可以轻易地通过挂载宿主机根目录并使用chroot
命令切换根目录,从而完全控制宿主机 - Docker Socket 挂载:另一种常见配置错误是直接将
/var/run/docker.sock
(Docker 守护进程的 Unix Socket)挂载到容器内部。这样做的后果是,容器内的进程可以直接与 Docker 守护进程通信,相当于拥有了在宿主机上创建、运行、停止任何容器的权限。攻击者可以利用这个权限创建另一个特权容器,将宿主机根目录挂载进去,然后轻松实现逃逸
3. 文件描述符泄漏与符号链接
最近的漏洞,如 CVE-2024-21626,则利用了另一种机制:
- 工作目录和文件描述符:这个漏洞是由于 runc 在处理容器进程的启动和工作目录时存在缺陷。攻击者可以利用
/proc/self/fd/
这个特殊目录,通过设置容器的工作目录或创建符号链接,来访问本不应该被容器访问到的宿主机文件描述符 - 突破命名空间隔离:容器通过命名空间(Namespaces)机制来隔离文件系统、进程、网络等资源。但是,如果攻击者可以找到一种方式,让容器内的进程能够操作宿主机上的文件句柄,那么就可以绕过这些命名空间的隔离,从而读写宿主机上的任意文件,最终实现逃逸