runC容器逃逸-CVE-2019-5736复现
2023-2-18
| 2024-5-28
0  |  阅读时长 0 分钟
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
现在问题就是在,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 容器
首先,/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 功能)
编译EXP
生成的EXP放到受害者容器内执行
受害者进入容器
注意受害者必须以 /bin/sh 进入容器才能正常执行命令
notion image

漏洞修复

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

参考

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