Awesome-POC 云安全专题:Docker cp 容器逃逸漏洞 CVE-2019-14271 原理剖析与实战复现

原创2026-10-14 22:02:031,653 阅读
文章标签:网络安全渗透测试应用安全知识库

Awesome-POC 云安全专题:Docker cp 容器逃逸漏洞 CVE-2019-14271 原理剖析与实战复现

本指南以 Awesome-POC 云安全漏洞知识库中的 Docker copy 漏洞导致容器逃逸 CVE-2019-14271 为核心,完整讲解 docker cp 命令背后的 docker-tar 帮助进程、Go cgo 与 libnss_*.so 动态库加载机制如何组合成一条"复制文件即逃逸"的完整攻击链。读者在阅读后,将掌握该漏洞的根因、影响版本、Ubuntu 18.04 漏洞环境搭建方法、端到端逃逸复现步骤,以及基于源码结构推断出的恶意共享库劫持原理与修复建议。

漏洞概述:一次"复制文件"操作引发的容器逃逸

docker cp 命令允许从容器、向容器中、或容器之间复制文件,语法与标准的 Unix cp 命令非常相似。例如要从容器中复制 /var/logs,语法为:

docker cp container_name:/var/logs /some/host/path

看似普通的文件复制操作,却在 Docker 19.03.8 之前被 CVE-2019-14271 赋予了"容器逃逸"的严重性——攻击者只要能让宿主机用户对恶意容器执行 docker cp,就有机会获取宿主机的完全 root 访问权限。

该漏洞的独特之处在于:逃逸的触发方是宿主机上的合法进程,而恶意代码的载体却藏在容器的文件系统里。理解这条链路,需要先弄清 docker cp 的内部实现。

漏洞原理深度解析

docker-tar:chroot 进入容器的"特权搬运工"

在复制过程中,Docker 使用了一个名为 docker-tar 的帮助进程。其工作流程为:

  1. docker-tar 通过 chroot 进入目标容器的根文件系统;
  2. 在容器内将请求的文件或目录打包(archive)成 tar 流;
  3. 将生成的 tar 数据传递给 Docker daemon;
  4. 由 daemon 在宿主机的目标目录中完成解包提取。

关键点在于:chroot 只改变了进程对文件系统根的"视角",并没有改变进程本身的权限与身份。docker-tar 仍然是运行在宿主机上、以 root 权限执行的进程。这意味着,任何能在该进程内被加载并执行的代码,都天然拥有宿主机的 root 能力——问题只在于如何把恶意代码"塞进"这个进程。

根因:Go cgo 与 libnss 动态库加载

有漏洞的 Docker 版本是用 Go v1.11 编译的。在该版本中,一些含有嵌入 C 代码(cgo)的包会在运行时动态加载共享库。这些包包括 net 和 os/user,二者都是 docker-tar 所使用的,而且在运行时会加载多个 libnss_*.so 库。

这一机制的背景是 Linux 系统的 NSS(Name Service Switch,名称服务切换) 框架:

  • glibc 提供的 getpwnam()、getpwuid() 等用户/组解析函数,通过 /etc/nsswitch.conf 配置文件决定调用哪个后端模块(本地文件、DNS、LDAP 等);
  • 这些后端模块正是 libnss_files.so.2、libnss_dns.so.2 一类的动态共享库;
  • Go 的 os/user 包在启用 cgo 时会调用上述 C 函数,net 包同理,从而触发 glibc 通过 dlopen 动态加载对应 NSS 模块。

漏洞的引爆点就在动态库的加载路径上:一般来说,库是从宿主机的文件系统加载的;但因为 docker-tar 已经 chroot 到了容器,动态链接器解析共享库路径时会以容器根为新的根目录,因此会从容器文件系统中加载这些库。也就是说,docker-tar 会加载并执行来源于容器、或由容器控制的代码。

攻击链:从恶意 .so 到宿主机 root

综合上述机制,完整的攻击链可以归纳为:

  1. 攻击者构造一个恶意 libnss_*.so(典型目标是 libnss_files.so.2),并将其放置到容器的 /lib/x86_64-linux-gnu/(或对应架构的库路径)下,覆盖原库;
  2. 宿主机用户(如运维人员)对该容器执行 docker cp;
  3. Docker daemon 启动 docker-tar,chroot 进入容器文件系统;
  4. docker-tar 运行时,Go 的 os/user/net 包触发 glibc 加载 NSS 模块;
  5. 动态链接器从容器内加载被替换的恶意 libnss_files.so.2,其构造函数中的恶意代码以宿主机 root 身份执行;
  6. 恶意代码完成逃逸,攻击者获得宿主机上的 root 代码执行权限。

从源码结构看,这类利用的核心在于"受害者进程的权限边界与文件系统视角不一致"——进程拥有宿主机的全部权限,却被诱骗加载了攻击者控制下的代码,本质上是一种针对动态链接信任边界的劫持。

漏洞影响范围与攻击场景

影响版本范围如下:

v18.09.9 <= Docker < v19.03.8

即 Docker 18.09.x 系列受影响版本(自 18.09.9 起)直至 19.03.7 均存在该漏洞,19.03.8 及之后的版本已修复。

可能的攻击场景为 Docker 用户从另一个 Docker 处复制文件:

  • 场景一:容器运行含有恶意 libnss_*.so 库的镜像(如攻击者构造的特制恶意镜像);
  • 场景二:容器中含有被攻击者替换的 libnss_*.so 库(攻击者已获得容器内文件系统写入能力,如通过容器内的另一个漏洞先立足)。

在这两种情况下,只要宿主机用户对该容器执行 docker cp(从容器向宿主机复制文件),攻击者都可以获取主机上的 root 代码执行权限。值得注意的是,该漏洞的触发条件相对隐蔽:受害者执行的是一条看似常规、低风险的管理命令。

环境搭建:Ubuntu 18.04 + Docker 19.03.0

复现该漏洞需要一个位于受影响区间内的 Docker 版本。以下以 Ubuntu 18.04 为例,使用 install_docker_19.03.0.sh 脚本安装 Docker 19.03.0:

#!/bin/bash
set -e
echo "[*] Removing old Docker versions (if any)..."
sudo apt remove -y docker docker-engine docker.io containerd runc || true

echo "[*] Unholding previously held Docker packages (if any)..."
sudo apt-mark unhold docker-ce docker-ce-cli containerd.io || true

echo "[*] Removing incorrect Docker sources..."
sudo rm -f /etc/apt/sources.list.d/docker.list || true
sudo sed -i '/download.docker.com/d' /etc/apt/sources.list

echo "[*] Adding Tsinghua University Docker mirror GPG key..."
wget -qO - https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

echo "[*] Adding Tsinghua University Docker mirror repository..."
echo "deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu bionic stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list

echo "[*] Updating package index..."
sudo apt update

echo "[*] Searching for Docker 19.03.0..."
VERSION_STRING=$(apt-cache madison docker-ce | grep 19.03.0 | head -n1 | awk '{print $3}')
if [ -z "$VERSION_STRING" ]; then
  echo "[*] Docker 19.03.0 not found"
  exit 1
fi
echo "[*] Found version: $VERSION_STRING"

echo "[*] Installing Docker version $VERSION_STRING ..."
sudo apt install -y docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io

echo "[*] Locking version to prevent automatic updates..."
sudo apt-mark hold docker-ce docker-ce-cli containerd.io

echo "[*] Installation complete, current version:"
docker --version

该脚本的关键步骤说明如下:

步骤 作用
apt remove 旧版本 清理可能存在的 docker、docker-engine、docker.io、containerd、runc,避免版本冲突
apt-mark unhold 解除之前可能设置的 apt 版本锁定
清理 Docker 源 删除错误的 /etc/apt/sources.list.d/docker.list 及旧源配置
添加清华镜像源 使用清华大学 Docker CE 镜像加速国内下载,bionic 对应 Ubuntu 18.04
apt-cache madison 查询版本 精确匹配 19.03.0 的版本字符串,避免安装到其他版本
apt install -y 指定版本 同时锁定 docker-ce、docker-ce-cli、containerd.io 三个组件版本
apt-mark hold 锁定版本,防止后续 apt upgrade 自动升级修复漏洞

安装完成后,可通过 docker --version 确认版本号为 19.03.0:

![Docker 19.03.0 安装完成后的版本验证](https://raw.gitcode.com/GitHub_Trending/aw/Awesome-POC/raw/0f9e22b660cb1ea12c1c3668f9128d93e08da412/云安全漏洞/images/Docker copy 漏洞导致容器逃逸 CVE-2019-14271/image-20250526134145172.png?utm_source=gitcode_repo_files)

关于漏洞环境版本选择的补充:Awesome-POC 的 Kubernetes + Ubuntu 18.04 漏洞环境搭建 文档在"容器逃逸"类别中收录了 CVE-2019-14271,并说明其漏洞环境基于 Docker 18.09.3/19.03.6 构建,同样落在受影响区间内,可作为搭建整套容器/集群漏洞靶场的参考。该文档也明确提示:此类安装脚本仅适用于漏洞复现环境,请勿用于生产环境。

漏洞复现:从恶意库替换到 root 逃逸

步骤 1:创建目标容器

新建一个名为 14271 的容器,镜像使用 ubuntu,运行 bash 保持存活:

sudo docker run -itd --name=14271 ubuntu bash

步骤 2:上传利用文件

将漏洞利用文件目录拷贝至容器内。利用文件来自 Metarget 项目的 docker-cve-2019-14271/exp 目录(该目录结构与 breakout、libnss_files.so.2、original_libnss_files.so.2 三个文件相关,具体解读见后文"利用文件结构与逃逸机制解读"):

sudo docker cp ./metarget/writeups_cnv/docker-cve-2019-14271/exp/ 14271:/

步骤 3:替换容器内原有的 NSS 库

进入容器,删除容器内原来的 .so 文件,替换为修改后的恶意 .so 文件:

sudo docker exec -it 14271 bash
root@7271b49ce3d1:/# ls /exp
breakout  libnss_files.so.2  original_libnss_files.so.2
root@7271b49ce3d1:/# cp /exp/* /
root@7271b49ce3d1:/# chmod 777 /breakout
root@7271b49ce3d1:/# touch /logs
root@7271b49ce3d1:/# rm /lib/x86_64-linux-gnu/libnss_files.so.2
root@7271b49ce3d1:/# mv /libnss_files.so.2 /lib/x86_64-linux-gnu/
root@7271b49ce3d1:/# exit
exit

各命令的意图如下:

  • ls /exp:查看利用文件,共包含 breakout(逃逸辅助程序)、libnss_files.so.2(恶意 NSS 库)、original_libnss_files.so.2(原始库备份)三个文件;
  • cp /exp/* /:将三个文件复制到容器根目录 /,便于后续操作;
  • chmod 777 /breakout:为 breakout 赋予可执行权限;
  • touch /logs:在容器根目录创建 /logs 文件,作为后续 docker cp 的复制目标,确保触发时 docker-tar 被拉起并执行归档流程;
  • rm 原始库 + mv 恶意库:删除容器内原有的 /lib/x86_64-linux-gnu/libnss_files.so.2,将恶意 libnss_files.so.2 移动到该标准库路径,确保动态链接器在容器根视角下加载到的是恶意版本。

步骤 4:在宿主机触发 docker cp 完成逃逸

替换完成后退出容器,回到宿主机执行 docker cp 命令,将容器内的 /logs 复制到当前目录:

sudo docker cp 14271:/logs ./

当这条命令执行时,Docker daemon 启动 docker-tar,后者 chroot 进容器并加载被替换的恶意 libnss_files.so.2,恶意代码随即以宿主机 root 权限执行。

步骤 5:验证逃逸结果

再次进入容器,检查根目录下是否存在 host_fs 目录:

sudo docker exec -it 14271 bash
root@7271b49ce3d1:/# ls -al / | grep host_fs
drwxr-xr-x  24 root root 4096 May 26 05:46 host_fs

![逃逸成功:容器根目录下出现挂载宿主机文件系统的 host_fs 目录](https://raw.gitcode.com/GitHub_Trending/aw/Awesome-POC/raw/0f9e22b660cb1ea12c1c3668f9128d93e08da412/云安全漏洞/images/Docker copy 漏洞导致容器逃逸 CVE-2019-14271/image-20250526135350026.png?utm_source=gitcode_repo_files)

host_fs 目录的出现意味着宿主机文件系统已经被 bind mount 到容器内部。此时攻击者已在容器内获得对整个宿主机文件系统的读写视角,可以进一步读取 /etc/shadow、植入 SSH 公钥、篡改系统文件或写入持久化后门,容器逃逸宣告完成。

利用文件结构与逃逸机制解读

从利用文件的三件套结构(breakout、libnss_files.so.2、original_libnss_files.so.2)以及复现流程可以推断其逃逸手法:

  • libnss_files.so.2(恶意库):在原始 glibc libnss_files.so.2 基础上植入恶意逻辑,利用 ELF 共享库的构造函数(constructor)机制,在 dlopen 加载时立即执行。此时进程处于 chroot 后的容器视角,但拥有宿主机 root 权限与完整的挂载能力;
  • breakout(逃逸辅助程序):恶意库加载后执行的落脚点。典型手法是借助进程当前工作目录(cwd)可能仍指向 chroot 根之外的特性,通过反复 chdir("..") 跳出 chroot 回到宿主机真实根目录,随后执行 breakout;breakout 通常负责在容器内创建 /host_fs 目录,并将宿主机根目录 / 以 bind mount 方式挂载进去,从而把宿主机文件系统"呈现"给容器内视角;
  • original_libnss_files.so.2(原始库备份):保存原始库文件,供恢复或对比分析使用,也说明利用制作过程是"基于原始库改造成恶意库"。

需要说明的是,上述细节是基于文件命名与复现流程的合理推断,仓库内并未包含该 exp 的源码;实际利用的精确实现以 Metarget 项目提供的文件为准。但无论实现细节如何,其本质始终一致:劫持宿主机进程(docker-tar)在容器文件系统中动态加载共享库的行为。

漏洞修复与防御建议

升级修复

最直接的修复方式是将 Docker 升级至修复该漏洞的版本(19.03.8 及之后,或包含修复的更高版本)。生产环境应通过官方渠道跟踪 Docker Engine 的版本更新,及时应用安全补丁。对于复现环境,同样建议在完成验证后立即升级,避免将存在高危逃逸漏洞的 Docker 暴露在可访问的网络中。

缓解与防御措施

从漏洞成因出发,可以结合实际情况考虑以下缓解措施:

  • 镜像信任边界:仅从可信来源拉取镜像,并对镜像内容进行审计,避免运行内含可疑动态库的恶意镜像(对应"场景一");
  • 容器内文件完整性:对容器文件系统建立基线并监控关键库文件(如 /lib/x86_64-linux-gnu/libnss_*.so)的变更,及时发现被替换的可疑共享库(对应"场景二");
  • 最小权限原则:限制普通用户对 docker 组和 Docker API 的访问,避免不可信用户触发针对敏感容器的 docker cp 操作;
  • 纵深防御:为 Docker daemon 与容器运行时配置 seccomp、AppArmor 等安全加固策略,降低共享库劫持类攻击成功后的影响面。

延伸阅读:Awesome-POC 中的容器逃逸系列

CVE-2019-14271 只是容器逃逸攻击面的一环。Awesome-POC 云安全漏洞知识库中收录了完整的容器/集群逃逸专题,可在 README.md 的"云安全漏洞"分类下交叉查阅:

  • Docker runC 漏洞导致容器逃逸 CVE-2019-5736:通过恶意容器覆盖 runC 二进制实现宿主机代码执行;
  • Containerd 漏洞导致容器逃逸 CVE-2020-15257:利用 containerd-shim API 暴露的 Unix socket 逃逸;
  • 挂载 docker.sock 导致容器逃逸、挂载宿主机 procfs 系统导致容器逃逸:利用错误挂载获得宿主机控制权;
  • Linux 内核 cgroup v1 逻辑错误导致容器逃逸 CVE-2022-0492:内核层面的逃逸利用。

这些文档与本文共同构成了从"应用层逃逸"到"内核层逃逸"的完整知识图谱,便于安全研究人员系统性地理解容器隔离边界的各类突破方式。

免责声明:本文内容仅用于安全研究与教学目的。对漏洞环境的搭建与复现,请在隔离的、获得授权的实验环境中进行,严禁用于任何未授权系统或生产环境。

登录后查看全文
Awesome-POC