跳转到帖子

授权渗透测试中如何验证 Kubernetes ServiceAccount 越权,并区分真实风险与误报

精选回复

发布于

在授权内网渗透或云原生安全评估中,经常会遇到这样的疑问:某个业务 Pod 使用的 ServiceAccount 被授予了较高 Kubernetes API 权限,是否可以直接判定为高危?更稳妥的做法是从“身份权限—实际可执行操作—敏感数据暴露—审计留痕”四个层面验证,而不是只看到一个 ClusterRoleBinding 就下结论。

背景与测试边界

以下场景假设测试范围已经获得书面授权,目标集群为测试或已明确纳入评估的生产集群。测试账号仅用于只读验证,不读取 Secret 内容、不创建 Pod、不修改 RBAC、不访问未授权命名空间。

示例环境:

  • 命名空间:payment-prod
  • 业务 ServiceAccount:payment-api
  • 待确认问题:该账号是否能够跨命名空间读取 Secret,或执行其他超出业务职责的操作
  • 测试主体:已获得授权的集群审计账号,或在测试 Pod 内对当前 ServiceAccount 做权限核验

问题定位:先确认身份,再确认授权来源

很多误报来自把“绑定存在”误认为“权限一定生效”。首先确认 ServiceAccount 是否确实被业务 Pod 使用,然后检查 RoleBinding、ClusterRoleBinding 及其引用的 Role 或 ClusterRole。

# 查看命名空间内使用该 ServiceAccount 的 Pod
kubectl -n payment-prod get pod \
  -o custom-columns='NAME:.metadata.name,SA:.spec.serviceAccountName' \
  | awk '$2=="payment-api"'

# 查看该 ServiceAccount 相关的命名空间级绑定
kubectl -n payment-prod get rolebinding -o yaml | \
  grep -B8 -A12 'payment-api'

# 查看集群级绑定
kubectl get clusterrolebinding -o yaml | \
  grep -B8 -A16 'payment-api'

# 查看被引用的角色规则
kubectl get clusterrole <role-name> -o yaml
kubectl -n payment-prod get role <role-name> -o yaml

重点关注以下字段:

  • subjects[].kind 是否为目标 ServiceAccount;
  • subjects[].namespace 是否与预期一致;
  • roleRef.kind 是 Role 还是 ClusterRole;
  • rules[].apiGroups、resources、verbs 是否包含敏感资源和高风险动作;
  • 是否存在 resources: ["*"]、verbs: ["*"] 或对 secrets、pods/exec、pods/portforward 的宽泛授权。

技术分析:使用 SelfSubjectAccessReview 做最小权限验证

直接遍历大量 RBAC YAML 容易遗漏聚合角色、组继承或默认角色。更可靠的方式是调用 Kubernetes 的授权检查接口,验证具体资源和动作是否允许。测试时应优先使用 kubectl auth can-i,它不会读取目标资源内容。

# 验证该 ServiceAccount 能否读取当前命名空间的 Secret
kubectl auth can-i get secrets \
  --as=system:serviceaccount:payment-prod:payment-api \
  -n payment-prod

# 验证是否可以列出其他命名空间的 Secret
kubectl auth can-i list secrets \
  --as=system:serviceaccount:payment-prod:payment-api \
  -n audit

# 验证是否具有创建 Pod、执行命令或建立端口转发的能力
kubectl auth can-i create pods \
  --as=system:serviceaccount:payment-prod:payment-api \
  -n payment-prod
kubectl auth can-i create pods/exec \
  --as=system:serviceaccount:payment-prod:payment-api \
  -n payment-prod
kubectl auth can-i create pods/portforward \
  --as=system:serviceaccount:payment-prod:payment-api \
  -n payment-prod

# 检查一组常见高风险权限
for resource in secrets pods/configmaps roles rolebindings clusterrolebindings; do
  for verb in get list create update patch delete; do
    result=$(kubectl auth can-i "$verb" "$resource" \
      --as=system:serviceaccount:payment-prod:payment-api \
      -n payment-prod 2>/dev/null)
    [ "$result" = "yes" ] && printf '%-20s %-8s %s\n' "$resource" "$verb" "$result"
  done
done

如果需要验证跨命名空间风险,应对多个明确授权的命名空间重复检查。不要用“读取全部 Secret 内容”作为证明;只要授权检查明确返回 yes,并能从角色规则解释其来源,就足以形成权限越界证据。

如何判断风险等级

验证结果风险判断说明
仅能读取本命名空间业务 ConfigMap通常为低风险或符合预期仍需确认 ConfigMap 中没有明文凭据、内部密钥或敏感配置。
能读取本命名空间 Secret中高风险需要进一步确认 Secret 是否包含数据库密码、云凭据、签名密钥等。
能列出或读取其他业务命名空间 Secret高风险属于明显的命名空间隔离失效,可能导致横向访问和凭据泄露。
能创建 Pod,且可挂载高权限节点路径或使用高权限 ServiceAccount严重风险可能突破容器边界并影响节点或其他工作负载,应立即停止进一步验证并通知负责人。
能执行任意 Pod 命令或创建端口转发视目标 Pod 权限而定需要结合目标 Pod 的身份、网络策略和挂载内容判断,不能单独按最高等级定性。

日志留痕与关联排查

验证过程中应记录时间、测试主体、命名空间、资源、动作和返回结果。Kubernetes 审计日志通常位于 API Server 或集中式日志系统中,具体路径取决于集群部署方式。

# 在具备审计日志访问权限的节点上,按 ServiceAccount 身份筛选请求
jq -r '
  select(.user.username == "system:serviceaccount:payment-prod:payment-api") |
  [.requestReceivedTimestamp, .verb, .objectRef.namespace,
   .objectRef.resource, .objectRef.name, .responseStatus.code] | @tsv
' /var/log/kubernetes/audit/audit.log

# 按资源和动作统计,辅助发现异常权限使用
jq -r '
  select(.user.username == "system:serviceaccount:payment-prod:payment-api") |
  [.verb, .objectRef.resource, (.objectRef.namespace // "-")] | @tsv
' /var/log/kubernetes/audit/audit.log | sort | uniq -c

如果集群没有启用审计日志,应在报告中单独列为治理缺陷。至少应覆盖 secrets、pods/exec、RBAC 对象和跨命名空间访问,并将请求主体、来源地址、响应码和失败原因纳入集中检索。

修复建议

  • 删除不必要的 ClusterRoleBinding,优先改为目标命名空间内的 RoleBinding。
  • 按照业务实际操作收敛资源和动作,避免使用通配符。读取 ConfigMap 不等于需要读取 Secret,读取 Pod 状态也不等于需要 pods/exec。
  • 为不同工作负载使用独立 ServiceAccount,不要让多个业务共享一个高权限账号。
  • 设置 automountServiceAccountToken: false,仅对确实需要访问 Kubernetes API 的 Pod 显式开启。
  • 对 Secret 访问配置审计规则,并将异常的跨命名空间访问接入告警。
  • 定期执行权限清单和实际调用日志的对照,清理长期未使用的角色规则。
# 示例:不需要访问 Kubernetes API 的工作负载
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-web
  namespace: payment-prod
spec:
  template:
    spec:
      serviceAccountName: payment-api
      automountServiceAccountToken: false
      containers:
      - name: web
        image: registry.example.local/payment/web:1.4.2

结论

这类问题的核心不是“是否存在一个高权限角色”,而是该权限是否能被真实业务身份使用、是否突破了命名空间边界、是否触及敏感资源,以及验证过程能否在审计日志中闭环。授权测试中建议坚持只读、最小化和可回滚原则:用 kubectl auth can-i 证明权限,用 RBAC 规则解释来源,用审计日志确认行为,最后再给出精确到资源和动作的修复建议。

网络流量与边界分析示意图
网络请求、日志与边界流量分析示意
服务器与云环境安全示意图
服务器、云资产与权限边界梳理示意

验证 ServiceAccount 越权时,建议把“RBAC 看起来权限很大”和“确实能形成攻击路径”分开。比较实用的做法是按以下顺序确认。

1. 先确认实际身份和令牌边界

如果使用取得的 SA Token 测试,先确认它对应的主体、受众和有效期,避免拿过期 Token 或非 apiserver audience 的 Token 得出误报:

kubectl --server= \
  --token="$TOKEN" \
  --certificate-authority=ca.crt \
  auth whoami

kubectl --server= \
  --token="$TOKEN" \
  --certificate-authority=ca.crt \
  auth can-i --list -n TARGET_NAMESPACE

也可以由具备 impersonate 权限的测试账号模拟指定 SA:

kubectl auth can-i --as=system:serviceaccount:app:app-sa \
  --list -n app

注意:使用 --as 只能说明该身份经过授权层后拥有什么权限,不等同于目标工作负载中一定能够取得并使用对应 Token。

2. 不要只看通配符,重点检查可闭环的权限组合

常见需要单独验证的路径包括:

  • get/list/watch secrets:是否能读取包含凭据的 Secret,尤其是其他 SA、镜像仓库或应用密钥。
  • create pods:是否可以指定权限更高的 serviceAccountName,或创建 privileged、hostPath、hostPID Pod。
  • create/update rolebindings:是否可以绑定现有高权限 Role/ClusterRole。
  • create/update roles:是否同时具备 escalate,或者本身已拥有将要写入 Role 的全部权限。
  • bind:是否能把高权限 ClusterRole 绑定给自身或可控主体。
  • impersonate:对象是否覆盖 users、groups 或 serviceaccounts。
  • create serviceaccounts/token:是否能为其他 SA 签发 Token。
  • nodes/proxy、pods/exec、pods/attach:是否可以间接进入其他工作负载或访问 kubelet。
SA="system:serviceaccount:app:app-sa"

kubectl auth can-i --as="$SA" get secrets -n app
kubectl auth can-i --as="$SA" create pods -n app
kubectl auth can-i --as="$SA" create rolebindings.rbac.authorization.k8s.io -n app
kubectl auth can-i --as="$SA" bind clusterroles.rbac.authorization.k8s.io
kubectl auth can-i --as="$SA" impersonate serviceaccounts
kubectl auth can-i --as="$SA" create serviceaccounts/token -n app
kubectl auth can-i --as="$SA" create pods/exec -n app
kubectl auth can-i --as="$SA" get nodes/proxy

kubectl auth can-i --list 基于 SelfSubjectRulesReview,聚合角色、Webhook Authorizer 或部分外部授权场景下结果可能不完整,因此关键权限最好逐项调用 can-i,再用服务端 dry-run 验证。

3. 用服务端 dry-run 验证“能否真正提交”

例如怀疑 SA 能创建 Pod 并挂载更高权限 SA,可以提交一个无副作用的 dry-run 请求:

cat > /tmp/sa-check.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: sa-escalation-check
  namespace: app
spec:
  serviceAccountName: privileged-sa
  restartPolicy: Never
  containers:
  - name: check
    image: registry.k8s.io/pause:3.9
EOF

kubectl --as=system:serviceaccount:app:app-sa \
  apply --dry-run=server -f /tmp/sa-check.yaml -o yaml

这里要看服务端最终返回的对象,而不只是请求是否通过:准入控制可能替换 serviceAccountName、禁止 Token 自动挂载,或者拒绝 privileged、hostPath 等字段。

怀疑可以绑定 cluster-admin 时,也应使用 dry-run 验证 RBAC 自身的防提权检查是否生效:

cat > /tmp/rb-check.yaml <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: escalation-check
  namespace: app
subjects:
- kind: ServiceAccount
  name: app-sa
  namespace: app
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
EOF

kubectl --as=system:serviceaccount:app:app-sa \
  create --dry-run=server -f /tmp/rb-check.yaml -o yaml

如果返回类似“attempting to grant RBAC permissions not currently held”,说明虽然主体可以创建 RoleBinding,但不能任意绑定该角色。这类情况通常不能直接定为集群管理员越权。

4. 区分真实风险与误报

  • 真实风险:授权检查通过,服务端 dry-run 也通过,并且目标对象实际存在、准入策略不阻断,最终能形成读取敏感数据、控制其他工作负载或集群级提权的闭环。
  • 受限风险:只能在本命名空间创建普通 Pod,无法选择其他 SA,也被 Pod Security、Kyverno、Gatekeeper 等策略限制。这通常是命名空间内工作负载控制,而不是集群接管。
  • 条件性风险:拥有 get secrets,但命名空间当前没有敏感 Secret;或者可创建 Pod,但高权限 SA 尚不存在。应记录为权限设计缺陷及成立条件,不宜直接宣称已越权。
  • 常见误报:只根据 Role/ClusterRole YAML 判断,没有计算 RoleBinding、命名空间、resourceNames、子资源和准入策略;或者将 create rolebindings 直接等同于可绑定 cluster-admin。

报告里最好把证据拆成“主体身份、有效 RBAC、服务端 dry-run 结果、阻断控制、最终影响”五部分。这样既能证明问题,又不会把理论权限夸大成实际集群接管。

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。