type
status
date
slug
summary
tags
category
icon
password
漏洞环境
环境部署
漏洞影响版本
- runC < 1.0-rc6 (docker < 18.09.2)
利用条件
需要在容器内具有 root 权限,才能覆盖runC文件
漏洞描述
runc through 1.0-rc6, as used in Docker before 18.09.2 and other products, allows attackers to overwrite the host runc binary (and consequently obtain host root access) by leveraging the ability to execute a command as root within one of these types of containers: (1) a new container with an attacker-controlled image, or (2) an existing container, to which the attacker previously had write access, that can be attached with docker exec. This occurs because of file-descriptor mishandling, related to /proc/self/exe.
环境版本信息
拉一下 docker 镜像
前置知识
漏洞描述中说漏洞主要由runC引起的,那什么是 runC?
runC 最初是Docker的一般分核心代码,用于底层容器运行时,被 Docker (“高层次”)调度用来运行容器,后面被单独开源出来了。这里的 “高层次” 是指 容器的镜像创建、管理,创建容器、进入正在运行中的容器 (docker exec) 等功能
为了防止容器内执行的命令对宿主机造成影响,runC 会创建一个 runC init 的带有命名空间限制的子进程,使得让其可以安全的运行在容器中,然后用户在容器调用命令时,底层通过运行execve系统调用 在新的(容器内)命名空间执行。创建新的容器和从宿主机进入到容器内也是这样操作的。
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Ffdca418b-aaff-4345-8b6d-ce9480137650%2FUntitled.png?table=block&id=3c89f4c0-6fac-4a29-b844-73ad81798efb&t=3c89f4c0-6fac-4a29-b844-73ad81798efb&width=1600&cache=v2)
现在问题就是在,runC init 运行时,是直接指向宿主机的 runC 的,也就是说攻击者通过重写 /proc/self/exe 的方式,待用户从宿主机进入容器时,即会篡改宿主机的 runC,最终运行篡改后的 runC 从而造成宿主机命令执行。
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7f1d34aa-67d6-409f-9200-699add95f9ab%2FUntitled.png?table=block&id=e2f9d8fc-595e-4343-b02e-8237f9780e0b&t=e2f9d8fc-595e-4343-b02e-8237f9780e0b&width=1479&cache=v2)
那为啥不直接篡改
/proc/[runc-init-pid]/exe
,非得受害者从宿主机进入才能触发漏洞呢?(毕竟篡改了runC,docker在执行很多容器相关操作时都会调用到runC,这样就可以做到被动出发这个漏洞了)这都从CVE-2016-9962说起,它是一个能遍历宿主机目录的漏洞。这个漏洞补丁,是在从宿主机进入容器前 runC init 进程不会影响至宿主机的文件,然后进入容器执行
execve
后会把相关标记位去除,即可影响宿主机上的runC。漏洞分析
进入拉取的 ubuntu 容器
首先,
/proc
目录是Linux里记录进程运行信息的虚拟文件系统(运行在内存中,非真正存在于硬盘中),一般都是挂载在 /proc
,我们可以认为是内核映射出来的,每个进程都有它自己的目录 /proc/<pid>
。然后 /proc/self
表示当前进程所在目录,是 /proc/<pid>
的软链接。对于本漏洞来说,进程目录有1个文件和1个目录需要关注的/proc/self/exe
当前进程运行着的可执行文件等软链接
/proc/self/fd
包含了当前进程打开的文件描述符
![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fae64853e-946c-4415-80d0-79ef6b5a0fcf%2FUntitled.png?table=block&id=8f32b856-1403-44e8-8f5c-5011fe4e39dd&t=8f32b856-1403-44e8-8f5c-5011fe4e39dd&width=1116&cache=v2)
运行
/proc/self/exe --help
发现是个 bash 程序,因为我们是运行 /bin/bash
进入的,并以 /bin/bash
执行,而 /proc/self
目录记录的是当前进程的运行信息,/proc/self/exe
是内核为每个进程创建的指向该进程执行二进制文件的软链接。如果在容器内执行 /bin/bash
,最终执行的是 /proc/self/exe
,然后 /proc/self/exe
指向宿主机的 runc
可执行文件。如果我们能够改写
/proc/self/exe
那么就可以改写 runc
,又因进入容器时执行的命令会执行 runc
, 那么我们就可以在宿主机上执行任意命令了。这里存在一个问题,当我们进入容器时,
runc
执行,但处理执行中的文件是无法被写入的,但它运行完我们 /proc/[runc-pid]/
目录又会不见了,我们需要记录它。刚刚提及到 /proc/self/fd
的作用,即我们发现 runc
在执行后,通过我们的脚本马上打开它 /proc/[runc-pid]/exe
,然后在 /proc/self/fd/xx
就能找到我们打开的 /proc/[runc-pid]/exe
,等 runc
执行完后,马上写入它,就可以修改宿主机的 runc
了漏洞利用
主要有两种利用方式
- 拉取一个攻击者构造的容器,启动后即触发重写
/proc/self/exe
的程序,可见此
- 攻击者获取到容器内的 root 权限,运行漏洞EXP,导致受害者在进入容器时宿主机 runc 被执行攻击者构造的命令。下面即演示这种方式
如果在本机实验,因为漏洞利用会重写 runC,所以请先备份一下 docker-runc 或 runc,或者给虚拟机打个快照(建议快照,本机尝试恢复 docker-runc 无法恢复 docker exec -it 功能)
编译EXP
生成的EXP放到受害者容器内执行
受害者进入容器
注意受害者必须以
/bin/sh
进入容器才能正常执行命令![notion image](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7ca809a1-83ab-4ea7-804e-c28f13d0f35f%2FUntitled.png?table=block&id=f94e1c39-c0c7-442e-864e-360cfe0f9474&t=f94e1c39-c0c7-442e-864e-360cfe0f9474&width=2754&cache=v2)
漏洞修复
从宿主机进入容器,即在执行
/proc/self/exe
时,通过 memfd_create
函数先为其在内存中创建一个宿主机 runc
的不可写( O_RDONLY | O_CLOEXEC
)临时副本,副本仅可读可执行,且与宿主机 runc
无关,即使能可写也不会影响到宿主机的 runc
从而避免容器逃逸