CVE-2021-30465:runc 竞争致 docker 逃逸
基础知识
在容器层面挂载卷和挂载目录是不一样的。
挂载目录,对容器来说,只是简单把目录与容器的目录做映射绑定,而目录的权限还是在主机,需要用户自制维护,手动处理权限等问题。
卷 (Volume) 是受控存储,挂载卷后是由容器引擎进行管理维护的,也就是把对应卷的所有权交给了容器引擎(本次漏洞的核心点)
正常定义卷A之后 ,若a容器挂载了卷A,那它同时也挂载了卷A下面的目录。
当a容器起来后,恶意程序疯狂的在挂载的目录下刷新软连接与目录的关系。与此同时若b容器也像a容器一样挂载了相同中的卷和对应卷下面的目录。
因为卷所有权这个时候是在引擎内,并且a容器相同卷下的目录还在刷新软连接(相同于创建软连接)这个时间在容器引擎内部就会存在资源竞争,形成了漏洞。
漏洞原理
runc在处理容器卷挂载时,会先使用filepath-securejoin库检查并解析路径中的符号链接以确保安全性,但这一“检查”动作与后续实际执行挂载的“使用”动作并非原子操作,其间存在一个极小的可被攻击的时间窗口。攻击者可在Kubernetes这类支持多容器共享卷的环境中,创建一个包含恶意容器与受害者容器的Pod,并利用符号链接交换(symlink-exchange)手法,在runc完成路径安全检查之后、执行挂载操作之前,迅速将共享卷中的目标目录替换为指向宿主机根目录的符号链接,从而诱骗runc在毫秒级的竞争窗口内将宿主机文件系统挂载进受害者容器,最终获得对宿主机文件的读写权限并实现逃逸。
漏洞对比
| 对比维度 | CVE-2021-30465 (runc 竞争致逃逸) | CVE-2018-15664 (Docker CP 逃逸) |
|---|---|---|
| 漏洞组件 | runc(容器运行时) | Docker daemon(API 及 docker cp 命令) |
| 攻击触发方式 | 容器创建/启动时,通过构造恶意卷配置触发 | 容器运行中,通过劫持外部发起的 docker cp 操作触发 |
| 攻击者前提 | 需要创建多个容器并配置共享卷 | 需要拥有执行 docker cp 命令的权限 |
| 攻击目标 | 将宿主机根目录挂载为容器内的一个卷 | 对宿主机文件系统进行任意读写(读或写任意文件) |
| 影响范围 | runc < 1.0.0-rc95 | Docker 17.06.0-ce 至 18.06.1-ce-rc2 |
| 修复状态 | 升级 runc 到 v1.0.0-rc95 或更高版本 | 官方提供了临时方案(如使用 docker pause),根本性修复需修改 Docker 底层逻辑 |
| 利用难度 | 较高:需要精确的时间窗口,在特定场景(如K8s)下更易实现 | 相对较低:有公开的、稳定的PoC,攻击路径更直接 |
| 漏洞利用结果 | 容器逃逸,获得对宿主机文件的读/写访问权限 | 容器逃逸,获得对宿主机文件的读/写访问权限 |
漏洞利用
创建攻击Pod

编译恶意程序

执行恶意程序
将恶意程序拷贝至c1容器内执行

进入 c1 容器创建符号链接并执行攻击脚本

接着更新c2-c20的容器镜像为合法镜像。新打开一个终端,执行以下命令更新镜像,一旦更新,pod会尝试创建容器

利用成功
