扫码领资料
获黑客教程
免费&进群
写在前面
分类讨论
0x01-基础设施
0x02-服务
0x03-归根到底
实践
创建Service
后渗透利用
实践场景1-使用UserAccount
实践场景2-使用ServiceAccount
写在最后
不久前看到一篇对Kubernetes授权管理的文章,笔者而后进行一些实验和思考,因此诞生了这篇学习笔记。本文章思路未必贴合实际应用场景,有概念错误的地方还望多多指正。
首先笔者对于Kubernetes用户的授权分为两个主体:对
的权限授予和对 的权限授予的权限,理想情况下按照集群结构划分:
master节点使用admin用户账户权限,这个权限是最大的
node节点使用普通用户权限,普通用户权限仅能对集群中的部分资源控制,或者是对某个namespcace中的资源进行控制
什么叫做对
进行划分呢?云计算的初衷是优雅地调配庞大规模的服务群,那么运维人员就需要对A、B、C…这些服务(Service)能够操纵集群权限这个能力进行考量,假如Service A由三台nginx容器构成,可能就不需要什么集群服务的资源。对于B服务而言,其定位是用来监控集群主机健康状态的,所以就需要很强的 权限,起码是对 、 有管理权限。在
中,每个服务容器都会被 下发一个默认的 账户(后文简称SA),而运维人员可以指定服务容器注入哪个SA账户。这意味着运维人员可以将不同SA账户注入给各类服务容器,来达到各类服务(A、B、C)权限细粒度化。和 的权限授予有相同之处,但又不完全相同。它们相同的地方在于概念,都是为了控制对集群资源操纵的能力,这一过程在 中叫做授权(Authoriazation),可以参考下图;不同的地方在于控制单元,基础设施的权限是以用户账户(User Account)的权限为单位的,而服务的权限是以SA账户(ServiceAccount)为单位划分的。
那么,有没有办法来优雅地控制对这两种权限授予方式的统一呢?答案很显然是有,而且不止一种。这里摘取一种比较好的做法:通过 分发权限。
例如管理员事先建立
而后,通过 或者 的方式将这两个 分发给不同的 或者 ,相当于是把锅碗瓢盆给到不同的角色。
假设一个场景,当
需要拥有所有Pod的可读权限,那么管理员(kubernetes admin)就通过 的形式将 绑定给 ;当 再想要要所有Pod的可写权限时,以相同的 方式将 绑定给 即可。最终,我们创建一个理想化的场景:把集群内所有可利用资源(api-resources)的
操作分别建立为不同的 ,在创建 与 时赋予它们最小权限,指定最少范围的 ,最好只作用于default namespcace。而后当 或者 需要对应权限时,管理员再经过审核制 ,分配 给这些 或者 ,而不是在初始化时就把权限塞满。下面以两个实践为例,理解前文提到的权限分发思路。表格为实验拓扑:一台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 |
master节点使用kubernetes-admin用户进行管理,其他两个node节点使用userA、userB用户进行管理,且需要满足的条件:
kubernetes-admin具有对集群操纵的全部权限
userA用户仅拥有对tenantA命名空间的Pod容器完全操作权限
userB用户仅拥有对tenantB命名空间的Pod容器完全操作权限
分析:由于
节点的用户配置文件 默认为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
第二步:创建两个
给 、 使用,这里笔者模拟的场景是租户根据 隔离[root@k8s-master ~]# kubectl create namespace tenant-a
namespace/tanent-a created
[root@k8s-master ~]# kubectl create namespace tenant-b
namespace/tanent-b created
第三步:创建
描述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"]
第四步:分别将
权限绑定给用户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
没有看错,我们使用的是
而不是 来完成权限绑定。前文提到, 是对某个 进行的权限绑定,而 是对整个集群做权限绑定,可以在 时可以通过 引用 ,从而将集群的限制策略转为对 的权限绑定第五步:使用kubectl apply描述资源对象,就能向
、 赋予了两个不同 的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
在真实的渗透场景中,攻击者大多是通过Web网站拿到了某台容器的控制权限。在Kubernetes集群中,每台容器在启动时会被注入default权限的
,位置在/var/run/secrets/kubernetes.io/serviceaccount,它是 (后文简称SA)的凭证为了展示较大权限的SA都能做哪些事情,现在我们在
命名空间下创建一个Thinkphp Web Service,对外暴露 端口。其Pod容器使用的SA定义为 ,并且 具有对当前 命名空间下Pod完全执行权限。第一步创建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
第二步,通过如下资源描述文件创建漏洞环境的
,将其暴露为 服务。指定Pod容器默认注入的SA账户为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漏洞建立
或上传假设我们已经拿到
服务的容器权限,那么在后续的实战中,攻击者大多通过 等工具枚举当前SA账户 都具有哪些权限
但 做的事情比较有限,如下图 代码段所示,它通过访问 端点来断言自己是否拥有 权限,但却没有甄别当前SA账户处于哪个 下。
由于当前容器的服务账号为 ,具备 权限,理论上拥有 命名空间下Pod容器完全执行权限,如果仅用 当脚本小子,无疑浪费大好的攻击机会。
但话说回来,攻击者在此面临两个问题:
盲视野攻击的状态下我们怎么能知道当前SA可以操作哪些命名空间呢?
如何确定SA账户所在的命名空间呢?
对于第一个问题,笔者暂时没有找到很好的解决方案。而第二个问题有一种取巧的解决方法:当我们尝试去访问
中任何越权的操作端点时, 会返回 错误,此时x代表当前SA账户所在的 ,y就代表当前SA账户的用户名
提取当前SA账户的 所属为 ,接着就能列出命名空间 中的所有容器,也能进行后续的渗透操作
curl --cacert ./ca.crt --header "Authorization: Bearer $(cat ./token)" -X GET https://kubernetes.default.svc/api/v1/namespaces/tenant-a/pods
本文对理想条件下的
授权进行了部分探究,但笔者比较好奇的是,业务态较广的云上业务,服务账号的授权行为不可能做到如此细粒度。那么有没有更快、更安全的做法将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视频资料及工具)
(部分展示)
往期推荐
看到这里了,点个“赞”、“再看”吧
文章引用微信公众号"白帽子左一",如有侵权,请联系管理员删除!