记一次 K8s 默认 ServiceAccount Token 自动挂载引发的横向权限泄露排查

一次因 default ServiceAccount Token 自动挂载导致权限泄露的真实排查记录,涵盖 Token 发现、权限枚举、横向移动路径与最终加固。

问题背景

在一次常规的集群巡检中,安全团队发现某业务命名空间下存在异常的跨命名空间 API 调用日志。进一步分析发现,攻击者通过获取 Pod 内默认 ServiceAccount 的 Token,成功列举了整个集群的 Secret 资源,并尝试读取敏感配置。幸运的是,由于 RBAC 收敛及时,未造成实质数据泄露,但暴露了集群在 ServiceAccount Token 管理上的系统性风险。

故障现象

  • 某业务 Pod 的 stdout 中频繁出现 kubectl get secrets --all-namespaces 的执行记录
  • 审计日志显示 system:serviceaccount:prod:default 用户在短时间内发起大量 LIST 请求
  • 部分 Secret 资源被尝试读取,但因 RBAC 限制返回 403
  • 安全扫描工具报出多处 Pod 使用了默认 ServiceAccount 且 automountServiceAccountToken: true

排查过程

1. 定位异常 Token 使用

首先通过 Kubernetes 审计日志定位异常主体:

1
grep '"username":"system:serviceaccount:prod:default"' /var/log/kube-apiserver/audit.log | jq '.requestURI' | sort | uniq -c | sort -rn | head -20

发现该 ServiceAccount 在 2 小时内发起了 1800+ 次 LIST 请求,目标资源包括 secrets、configmaps、serviceaccounts 等。

2. 确认 Token 来源

进入可疑 Pod 检查挂载情况:

1
2
kubectl exec -it <pod-name> -n prod -- ls -la /var/run/secrets/kubernetes.io/serviceaccount/
kubectl exec -it <pod-name> -n prod -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 100

确认 Pod 确实自动挂载了 default ServiceAccount 的 Token,且该 Token 具备 system:serviceaccount:prod:default 身份。

3. 枚举该 Token 的实际权限

使用 Token 进行权限自检:

1
2
3
4
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -k -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default.svc/apis/authorization.k8s.io/v1/selfsubjectaccessreviews \
  -X POST -d '{"kind":"SelfSubjectAccessReview","apiVersion":"authorization.k8s.io/v1","spec":{"resourceAttributes":{"verb":"list","resource":"secrets"}}}' | jq

返回结果显示该 Token 对多个命名空间的 secrets 资源具备 list 权限,确认存在权限过大问题。

4. 追溯 Token 被滥用的路径

通过 Falco 规则捕获异常进程:

1
2
3
4
5
6
7
8
- rule: K8s API from non-kubectl binary
  condition: >
    proc.name != "kubectl" and
    proc.name != "kubelet" and
    container.id != host and
    k8s.pod.name != "" and
    evt.type in (connect,accept) and
    fd.sip="10.96.0.1"

最终发现异常进程为业务容器内的一个 Python 脚本,通过 kubernetes SDK 使用默认 Token 进行服务发现。

解决方案

  1. 立即禁用自动挂载:在受影响的 Deployment 中显式设置 automountServiceAccountToken: false

  2. 创建最小权限 ServiceAccount:为业务 Pod 单独创建 SA,并绑定仅包含必要权限的 Role/ClusterRole

  3. 启用 Token 投影:配置 projected volume,使用 expirationSeconds 限制 Token 有效期(建议 1h 以内)

  4. 审计并清理默认 SA 的权限:执行 kubectl get rolebinding,clusterrolebinding --all-namespaces -o yaml | grep -B5 'system:serviceaccount:.*:default',逐一收紧

根因分析

根本原因是 Kubernetes 早期版本默认行为允许 Pod 自动挂载 default ServiceAccount Token,且该 Token 继承了命名空间内默认的宽松 RBAC 策略。在多租户或敏感业务场景下,这一设计导致任意 Pod 都可能成为横向移动的起点。

预防措施

  • 在集群级别通过 Admission Controller 强制要求所有 Pod 显式声明 automountServiceAccountToken
  • 定期运行 kubectl auth can-i --list --as=system:serviceaccount:<ns>:default 进行权限巡检
  • 对高敏感命名空间启用 PodSecurityPolicyPod Security Standards,禁止使用 default SA
  • 在 CI/CD 流水线中加入 Token 权限扫描步骤,阻止宽权限配置上线
  • 考虑启用 BoundServiceAccountTokenVolume(GA 后默认开启),并设置合理的 audience 和过期时间

总结

一次看似普通的「默认 Token 自动挂载」配置,最终演变为潜在的横向权限泄露入口。通过本次排查,我们深刻认识到:在 Kubernetes 安全实践中,「默认即安全」往往是最大的误区。只有坚持最小权限原则、持续审计 ServiceAccount 权限,才能真正筑牢集群的身份防线。

使用 Hugo 构建
主题 StackJimmy 设计