发布于14小时前14小时 在授权内网渗透或云原生安全评估中,经常会遇到这样的疑问:某个业务 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 规则解释来源,用审计日志确认行为,最后再给出精确到资源和动作的修复建议。 网络请求、日志与边界流量分析示意 服务器、云资产与权限边界梳理示意
13小时前13小时 验证 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 结果、阻断控制、最终影响”五部分。这样既能证明问题,又不会把理论权限夸大成实际集群接管。
创建帐户或登录后发表意见