对Kubernetes RBAC授权防御&攻击的部分思考
2023-6-20 00:0:0 Author: hpdoger.cn(查看原文) 阅读量:3 收藏

不久前看到一篇对Kubernetes授权管理的文章,笔者而后进行一些实验和思考,因此诞生了这篇学习笔记。本文章思路未必贴合实际应用场景,有概念错误的地方还望多多指正。

首先笔者对于Kubernetes用户的授权分为两个主体:对基础设施的权限授予和对服务的权限授予

0x01-基础设施

基础设施的权限,理想情况下按照集群结构划分:

  • master节点使用admin用户账户权限,这个权限是最大的
  • node节点使用普通用户权限,普通用户权限仅能对集群中的部分资源控制,或者是对某个namespcace中的资源进行控制

0x02-服务

什么叫做对服务进行划分呢?云计算的初衷是优雅地调配庞大规模的服务群,那么运维人员就需要对A、B、C…这些服务(Service)能够操纵集群权限这个能力进行考量,假如Service A由三台nginx容器构成,可能就不需要什么集群服务的资源。对于B服务而言,其定位是用来监控集群主机健康状态的,所以就需要很强的CURD权限,起码是对PodDeployment有管理权限。

同时在Kubernetes中,每个服务容器都有默认账户叫做ServiceAccount,意味着我们需要通过控制SA账户来控制不同服务(A、B、C)应该具有怎样的细粒度权限。

0x03-归根到底

对于基础设施服务的权限授予有相同之处,但又不完全相同。它们相同的地方在于概念,都是为了控制对集群资源操纵的能力,这一过程在Kubernetes中叫做授权(Authoriazation),可以参考下图;不同的地方在于控制单元,基础设施的权限是以用户账户(User Account)的权限为单位的,而服务的权限是以SA账户(ServiceAccount)为单位划分的。

Untitled
那么,有没有办法来优雅地控制对这两种权限授予方式的统一呢?答案很显然是有,而且不止一种。这里摘取一种比较好的做法:通过ClusterRole分发权限。

例如管理员事先建立Pod-read-Rule、Pod-Write-Rule分别用来对Pod容器赋予可读与可写的操作权限,当然集群的资源可不止有Pod容器那么简单,我们可以通过kubectl api-resources例举哪些资源可以被约束。
Untitled
而后,通过RoleBinding或者ClusterBinding的方式将这两个ClusterRole分发给不同的User Account或者ServiceAccount,相当于是把锅碗瓢盆给到不同的角色。
我们假设一个场景,当User Account A需要对所有Pod的可读权限,那么管理员(kubernetes admin)就通过RoleBinding的形式将Pod-read-Rule绑定给User Account A;当A用户再想要对Pod拥有可写权限时,以相同的RoleBinding方式将Pod-Write-Rule绑定给User Account A即可。

最终,我们创建一个理想化的场景:把集群内所有可利用资源(api-resources)的CURD操作分别建立为不同的ClusterRole,在生成Service Account和User Account时对它们赋予最小权限,指定最少范围的namespace,最好只作用于default namespcace。而后当SA或者UA需要对应权限时,管理员再经过审核制RoleBinding,分配权限给这些账户或者SA,而不是在初始化时就把权限塞满。

Untitled

下面以两个实践为例,理解前文提到的权限分发思路。表格为实验拓扑:一台Centos7.x作为Master节点,剩下两台作为Node节点

主机 IP地址 内核版本 用户账户
k8s-master 192.168.56.80 3.10.0-1160.el7.x86_64 kubernetes-admin
k8s-node1 192.168.56.81 3.10.0-1160.el7.x86_64 userA
k8s-node2 192.168.56.82 3.10.0-1160.el7.x86_64 userB

实践场景1-使用UserAccount

master节点使用kubernetes-admin用户进行管理,其他两个node节点使用userA、userB用户进行管理,且需要满足的条件:

  • kubernetes-admin具有对集群操纵的全部权限
  • userA用户仅拥有对tenantA命名空间的Pod容器完全操作权限
  • userB用户仅拥有对tenantB命名空间的Pod容器完全操作权限

分析:由于master节点的用户配置文件/etc/kubernetes/admin.conf默认kubernetes-admin权限,所以我们无需关心,只需满足UserA、B的需求。

第一步:建立两个Kubernetes User Account分别为userA、userB,参考文章:k8s创建用户账号——User Account - fat_girl_spring - 博客园 (cnblogs.com)。完整的创建过程如下,以相同的方式创建两个用户即可

[[email protected] tenantA]# (umask 077;openssl genrsa -out userA.key 2048)
[[email protected]master tenantA]# openssl req -new -key userA.key -out userA.csr -subj "/O=k8s/CN=userA"
[[email protected] tenantA]# openssl  x509 -req -in userA.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out userA.crt -days 365

[[email protected] tenantA]# kubectl config set-cluster k8s --server=https://192.168.56.80:6443 --certificate-authority=/etc/kubernetes/pki/ca.crt --embed-certs=true --kubeconfig=/root/k8s_tenants/userA/userA.conf

[[email protected] tenantA]# kubectl config set-credentials userA --client-certificate=userA.crt --client-key=userA.key --embed-certs=true --kubeconfig=/root/k8s_tenants/userA/userA.conf

[[email protected] tenantA]# kubectl config set-context [email protected] --cluster=k8s --user=userA --kubeconfig=/root/k8s_tenants/userA/userA.conf

[[email protected] tenantA]# kubectl config use-context [email protected] --kubeconfig=/root/k8s_tenants/userA/userA.conf

[[email protected] tenantA]# kubectl get pods --kubeconfig ./userA.conf

第二步:创建两个namespcace给userA、userB使用,这里笔者模拟的场景是租户根据namespace隔离

[[email protected] ~]# kubectl create namespace tenant-a
namespace/tanent-a created
[[email protected] ~]# kubectl create namespace tenant-b
namespace/tanent-b created

第三步:创建ClusterRole描述Pod容器完全操作权限

# cluster_pod_all_permission_role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-pod-all-permission-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch", "create", "update", "delete"]

第四步:分别将cluster-pod-all-permission-role权限绑定给用户A/B,并指定相对应的命名空间

# usera_pod_rolebiding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenanta-pod-all-permission-role-binding
  namespace: tenant-a
subjects:
- kind: User
  name: userA
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-pod-all-permission-role
  apiGroup: rbac.authorization.k8s.io
---
# userb_pod_rolebiding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tenantb-pod-all-permission-role-binding
  namespace: tenant-b
subjects:
- kind: User
  name: userB
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-pod-all-permission-role
  apiGroup: rbac.authorization.k8s.io

没有看错,我们使用的是RoleBinding而不是ClusterRolebinding来完成权限绑定。前文提到,RoleBinding是对某个namespace进行的权限绑定,而ClusterRolebinding是对整个集群做权限绑定,可以在RoleBinding时可以通过roleRef引用ClusterRolebinding,从而将集群的限制策略转为对namespace的权限绑定

Untitled

第五步:使用kubectl apply描述资源对象,就能向userA、userB赋予了两个不同namespace的Pod完全执行权限,过程如下:

[[email protected] clusterrole]# kubectl apply -f cluster_pod_all_permission_role.yaml 
clusterrole.rbac.authorization.k8s.io/cluster-pod-all-permission-role created

[[email protected] clusterrole]# kubectl apply -f user_pod_rolebind.yaml 
rolebinding.rbac.authorization.k8s.io/tenanta-pod-all-permission-role-binding unchanged
rolebinding.rbac.authorization.k8s.io/tenantb-pod-all-permission-role-binding created

最后:我们来验证一下两个用户是否只能在当前租户空间(namespace)中操作Pod容器。如下图所示,k8s-node1使用userA作为用户账户,k8s-node2使用userB作为用户账户,它们仅能操作自身所在的namespace,除此之外无法访问任何namespace

Untitled

Untitled

实践场景2-使用ServiceAccount

在真实的渗透场景中,攻击者大多是通过Web网站拿到了某台容器的控制权限。在Kubernetes集群中,每台容器在启动时会被注入default权限的ServiceToken,位置在/var/run/secrets/kubernetes.io/serviceaccount,它是ServiceAccount(后文简称SA)的凭证

Untitled

为了展示较大权限的SA都能做哪些事情,现在我们在tanent-a命名空间下创建一个Thinkphp Web Service,对外暴露30080端口。其Pod容器使用的SA定义为phpsa,并且phpsa具有对当前tanent-a命名空间下Pod完全执行权限。

创建Service

第一步创建phpsa,并且赋予其tanent-a命名空间下对Pod容器完全执行权限

#phpsa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: phpsa
  namespace: tenant-a

---
apiVersion: rbac.authorization.k8s.io/v1  
kind: RoleBinding
metadata:
  name: phpsa-role-binding
  namespace: tenant-a
subjects:
- kind: ServiceAccount
  name: phpsa
  namespace: tenant-a
roleRef:
  kind: ClusterRole
  name: cluster-pod-all-permission-role
  apiGroup: rbac.authorization.k8s.io

第二步,通过如下资源描述文件创建漏洞环境的Deployment,将其暴露为Service服务。指定Pod容器默认注入的SA账户为phpsa

apiVersion: apps/v1
kind: Deployment
metadata:
  name: thinkphp5023-rce
  namespace: tenant-a
  labels:
    app: thinkphp5023-rce
spec:
  replicas: 2
  selector:
    matchLabels: # 跟template.metadata.labels一致
      app: thinkphp5023-rce
  template:
    metadata:
      labels:
        app: thinkphp5023-rce
    spec:
      containers:
      - name: thinkphp5023-rce-container
        image: vulhub/thinkphp:5.0.23
        ports:
        - containerPort: 80
          name: thinkphp-port
      serviceAccount: phpsa

---
apiVersion: v1
kind: Service
metadata:
  name: thinkphp5023-vulnerable-service
  namespace: tenant-a
spec:
  type: NodePort
  selector: # 更Deployment中的selector一致
    app: thinkphp5023-rce
  ports:
      # By default and for convenience, the `targetPort` is set to the same value as the `port` field.
    - port: 80
      name: thinkphp-port
      # Optional field
      # By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
      nodePort: 30081

Untitled

到这里,一个具有RCE漏洞的thinkphp5.0.23版本就搭建完毕了,攻击者可以通过one post rce漏洞拿到reverse shell或建立webshell

后渗透利用

假设我们已经拿到tp服务的容器权限,那么在后续的实战中,攻击者大多通过cdk等工具枚举phpsa都具有哪些权限

Untitled

Untitled
但cdk做的事情比较有限,它通过访问api/v1/namespaces端点来断言自己是否拥有list namespaces权限,但是没有甄别当前SA账户处于哪个namespace下。由于当前容器的服务账号为phpsa,具备cluster-pod-all-permission-role权限,理论上拥有tenant-a命名空间下Pod容器完全执行权限,如果仅用cdk当脚本小子,无疑浪费大好的攻击机会。

那么话说回来,攻击者在这里面临两个问题:

  1. 盲视野攻击的状态下我们怎么能知道当前SA可以操作哪些命名空间呢?
  2. 如何确定SA账户所在的命名空间呢?

对于第一个问题,笔者暂时没有找到很好的解决方案。而第二个问题有一种取巧的解决方法,当我们尝试去访问api server中任何越权的操作端点时,api server会返回serviceaccount:x:y cannot list resource错误,此时x代表当前SA账户所在的namespace,y代表当前SA账户的用户名

Untitled
提取当前SA账户的namespace所属为tenant-a,接着就能列出命名空间tenant-a中的所有容器,也能进行后续的渗透操作

curl --cacert ./ca.crt --header "Authorization: Bearer $(cat ./token)" -X GET https://kubernetes.default.svc/api/v1/namespaces/tenant-a/pods

Untitled

本文对理想条件下的Kubernetes授权进行了部分探究,但笔者比较好奇的是,业务态较广的云上业务,服务账号的授权行为不可能做到如此细粒度。那么有没有更快、更安全的做法将user group、namespace、准入控制玩出花活儿,从而让授权过程扁平化一些?先埋一个坑,等笔者有时间再探究一下


文章来源: https://hpdoger.cn/2023/06/20/title:%20%E5%AF%B9Kubernetes%20RBAC%E6%8E%88%E6%9D%83%E9%98%B2%E5%BE%A1&%E6%94%BB%E5%87%BB%E7%9A%84%E9%83%A8%E5%88%86%E6%80%9D%E8%80%83/
如有侵权请联系:admin#unsafe.sh