Docker 的容器預設使用 root (id=0)身分去執行程式,也因此即使在不使用特權容器的情況下,該帳號仍然擁有一些權限可以做事情,像是 apt 安裝套件、安裝驅動程式之類的,或是存取 root 權限的檔案。
而 Non-Root User 容器的概念就是指定容器使用低權限的身分去執行程式,如此一來即便駭客拿下容器的控制權也只有一般使用的身分,無法再進一步執行高權限的動作,相關資料可以參考Docker Security Cheat Sheet - RULE #2 - Set a user。
Non-Root User 的作法是在撰寫 Dockerfile 的時候,新增使用者身分並且切換過去,Dockerfile 調整如下。至於裡面用到的 suid_test 檔案內容可以參考 Day14 - Pwnkit 如果沒有 SUID,那跟鹹魚有什麼分別?。
FROM aeifkz/my-ubuntu:v1.0
ADD suid_test /tools
RUN chown root:root /tools/suid_test && chmod 4755 tools/suid_test
RUN useradd -u 8877 aeifkz
USER aeifkz
CMD ["python3","/tools/reverse_server.py"]
編譯 image 檔案並且執行。
docker build --no-cache --tag non-root . ;
docker run --rm -it non-root bash;
#看一下身分是誰?
whoami ;
#測試套件資訊更新,會失敗
apt update ;
#透過程式提權
/tools/suid_test ;
#看一下身分,所以代表還是可以提權
whoami ;
#測試套件資訊更新,會成功
apt update ;
# 當然還是可以用 root 身分連進去,只是要用 -u 參數指定 uid
docker exec -it -u 0 [container_name] bash;
利用此容器測試之前練過的容器逃逸手法,會發現在不討論提權的情況下,這個防禦機制能夠阻擋蠻多的攻擊手法,所以算是相當值得投資的一塊。但萬一在容器中存在可以提權的弱點時(ex: /tools/suid_test),還是可以於提權後再使用原本的攻擊手法。
攻擊手法 | Non Root User 能否阻擋? |
---|---|
privileged + host pid | Y |
--cap-add=ALL + host pid | Y |
privileged | Y |
(CVE-2022-0492) unshare + cgroup 特權逃逸手法 | Y |
安裝 linux_module | Y |
docker.sock 掛載 | Y |
這邊有個觀念要小小的勘誤一下,也就是 (CVE-2022-0492) unshare + cgroup 特權逃逸手法這個部分,剛好可以藉由 Non-Root User 這個防禦機制來做個小驗證。
# 有檔案應該就是 cgroup v2 版本
cat /sys/fs/cgroup/cgroup.controllers ;
# 調整 GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0"
sudo vim /etc/default/grub ;
sudo update-grub ;
sudo shutdown -r now ;
# 啟動容器並且不啟動安全防護機制 seccomp、apparmor
docker run --rm -it --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" aeifkz/my-ubuntu:v.non-root bash ;
# 目前該容器不是特權容器
cat /proc/$$/status | grep CapEff ;
# 複習一下 cgroup 逃逸手法前半段
mkdir /tmp/cgroup ;
mount -t cgroup -o rdma cgroup /tmp/cgroup ;
# 偷看一下目前的 uid 對應,格式為 ID-inside-ns ID-outside-ns length
# ID-inside-ns 表示在容器顯示的UID
# ID-outside-ns 表示容器外映射的真實的UID
# length 表示映射的範圍
cat /proc/$$/uid_map ;
# 透過 CVE-2022-0492 漏洞呼叫
unshare -UrmC --propagation=unchanged bash ;
# 看起來全部的能力都有,而且也是 root 帳號
whoami ;
cat /proc/$$/status | grep CapEff ;
# 如果剛剛沒提權,這邊連 apt update 都不能做
apt update ;
# unshare 建立新的 namespace 就可以呼叫 mount
mount -t cgroup -o rdma cgroup /tmp/cgroup ;
# 測試驅動程式逃逸手法看看,會發現無法成功安裝驅動程式
insmod test.ko ;
# 最後再偷看一次目前的 uid 對應
cat /proc/$$/uid_map ;
# 退回容器
exit ;
其實 unshare 下的參數有幫忙建立一個新的 user namespace,這部分會在 user remapping 的防禦手法再做說明,但即便在新的 namespace 有 root 身分,但仍然無法進行高權限的操作。
也因此從現在這個角度來看期中考的判斷流程還是有瑕疵的,因為當初第一步是先判斷 /proc/$$/status 內回傳的內容去判斷容器擁有的能力,但透過 unshare 開出新的 namespace 可能會造成誤判。所以要判斷 privileged container 的方式包含如參考資料所示 :
作業10-1 : 又來到我們的大型翻車現場,這車足足翻了有一年了我現在才發現。首先這次 Day01 - 開賽 (含作業1) 請各位建立的 Linux kernel - v5.16.20(Ubuntu) 其實這版是無法使用 CVE-2022-0492 這個弱點的,請參考資訊解釋一下為何不能? 以及更新內核版本後試著利用弱點進行逃逸會遇到甚麼問題?
作業10-2 : 既然翻車了就要撥亂反正,請建立以下環境並試著參考New Linux Vulnerability CVE-2022-0492 Affecting Cgroups: Can Containers Escape?實踐利用 CVE-2022-0492 弱點後接續 cgroups 的逃逸手法,並試著比較跟作業10-1環境的差異。建立環境參考如下 :
今日總結 :