对Kubernetes RBAC授权防御&攻击的部分思考

新闻资讯   2023-06-30 12:09   123   0  

扫码领资料

获黑客教程

免费&进群



  1. 写在前面

  2. 分类讨论

    1. 0x01-基础设施

    2. 0x02-服务

    3. 0x03-归根到底

  3. 实践

    1. 创建Service

    2. 后渗透利用

    3. 实践场景1-使用UserAccount

    4. 实践场景2-使用ServiceAccount

  4. 写在最后

写在前面

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

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

分类讨论

0x01-基础设施

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

  • master节点使用admin用户账户权限,这个权限是最大的

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

0x02-服务

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

Kubernetes中,每个服务容器都会被kubelet下发一个默认的ServiceAccount账户(后文简称SA),而运维人员可以指定服务容器注入哪个SA账户。这意味着运维人员可以将不同SA账户注入给各类服务容器,来达到各类服务(A、B、C)权限细粒度化。

0x03-归根到底

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


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

例如管理员事先建立Pod-read-RulePod-Write-Rule规则,分别用来制约对Pod容器可读、可写这两种操作,当然集群的资源可不止有Pod容器,我们可以通过kubectl api-resources例举哪些资源可以被ClusterRole约束。

而后,通过RoleBinding或者ClusterBinding的方式将这两个ClusterRole分发给不同的User Account或者ServiceAccount,相当于是把锅碗瓢盆给到不同的角色。

假设一个场景,当User Account A需要拥有所有Pod的可读权限,那么管理员(kubernetes admin)就通过RoleBinding的形式将Pod-read-Rule绑定给User Account A;当User Account A再想要要所有Pod的可写权限时,以相同的RoleBinding方式将Pod-Write-Rule绑定给User Account A即可。

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

实践

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

主机IP地址内核版本用户账户
k8s-master192.168.56.803.10.0-1160.el7.x86_64kubernetes-admin
k8s-node1192.168.56.813.10.0-1160.el7.x86_64userA
k8s-node2192.168.56.823.10.0-1160.el7.x86_64userB

实践场景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)。完整的创建过程如下,以相同的方式创建两个用户即可

[root@k8s-master tenantA]# (umask 077;openssl genrsa -out userA.key 2048)
[root@k8s-master tenantA]# openssl req -new -key userA.key -out userA.csr -subj "/O=k8s/CN=userA"
[root@k8s-master 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

[root@k8s-master 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

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

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

[root@k8s-master tenantA]# kubectl config use-context kubernetes-userA@k8s --kubeconfig=/root/k8s_tenants/userA/userA.conf

[root@k8s-master tenantA]# kubectl get pods --kubeconfig ./userA.conf

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

[root@k8s-master ~]# kubectl create namespace tenant-a
namespace/tanent-a created
[root@k8s-master ~]# 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的权限绑定

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

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

[root@k8s-master 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

实践场景2-使用ServiceAccount

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

为了展示较大权限的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

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

后渗透利用

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


cdk做的事情比较有限,如下图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账户的用户名


提取当前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

写在最后

本文对理想条件下的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/


声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权

@
学习更多渗透技能!体验靶场实战练习

hack视频资料及工具

(部分展示)



往期推荐

给第一次做渗透项目的新手总结的一些感悟

「登陆页面」常见的几种渗透思路与总结!

突破口!入职安服后的经验之谈

红队渗透下的入口权限快速获取

攻防演练|红队手段之将蓝队逼到关站!

CNVD 之5000w通用产品的收集(fofa)

自动化挖掘cnvd证书脚本

Xray捡洞中的高频漏洞

实战|通过供应链一举拿下目标后台权限

实战|一次真实的域渗透拿下域控(内网渗透)

看到这里了,点个“赞”、“再看”吧


文章引用微信公众号"白帽子左一",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。