跳转到帖子

Kubernetes 授权测试中发现 ServiceAccount 可创建 Pod,如何判断是否构成权限提升?

精选回复

发布于

问题背景

在一次已授权的 Kubernetes 内网渗透测试中,发现 dev 命名空间内的 ServiceAccount ci-runner 无权直接读取 Secret,但拥有创建 Pod 的权限。此时是否应判定为 Secret 读取或权限提升?如何在不接触生产敏感数据的前提下完成验证,并给出可落地的修复建议?

这个问题不能只看 get secrets 的结果。Kubernetes 的权限边界由 RBAC、Pod 准入策略、ServiceAccount、节点挂载能力和网络出口共同决定。主体只要能创建 Pod,通常就可能让 kubelet 代为挂载命名空间内的 Secret,或者让新 Pod 使用权限更高的 ServiceAccount。因此,单独执行一次 kubectl auth can-i get secrets 不足以证明边界安全。

问题定位

建议先确认测试身份、目标命名空间和授权范围。以下操作默认已经获得集群所有者授权,优先使用只读查询和服务端 dry-run。若 impersonation 返回 403,不应自行申请或绕过权限,应由集群管理员代为导出结果。

CTX=prod-cluster
NS=dev
SUBJECT=system:serviceaccount:dev:ci-runner

# 查看 RBAC 计算后的权限;该结果不包含准入控制和网络策略影响
kubectl --context="$CTX" auth can-i \
  --as="$SUBJECT" --list -n "$NS"

# 对关键动作逐项确认
for check in \
  "create pods" \
  "get secrets" \
  "list secrets" \
  "create deployments.apps" \
  "create jobs.batch" \
  "create pods/exec" \
  "create pods/attach" \
  "create serviceaccounts" \
  "create rolebindings.rbac.authorization.k8s.io"; do
  kubectl --context="$CTX" auth can-i $check \
    --as="$SUBJECT" -n "$NS"
done

# 导出命名空间内的绑定关系
kubectl --context="$CTX" get rolebinding -n "$NS" -o yaml
kubectl --context="$CTX" get role -n "$NS" -o yaml

# 检查该主体是否被 ClusterRoleBinding 直接绑定
kubectl --context="$CTX" get clusterrolebinding -o json | \
  jq --arg s "$SUBJECT" '
    .items[] |
    select(any(.subjects[]?;
      ((.kind + ":" + (.namespace // "") + ":" + .name) ==
       ("ServiceAccount:dev:ci-runner")))) |
    {name: .metadata.name, roleRef: .roleRef}'

kubectl auth can-i --list 只反映 RBAC 授权结果,不会告诉你 Pod Security Admission、Kyverno、Gatekeeper、镜像策略或 NetworkPolicy 是否会阻止后续动作,因此必须继续检查准入边界。

技术分析

需要重点判断以下几条链路:

  • Secret 间接访问:创建 Pod 时引用 Secret,API Server 通常不会要求 Pod 创建者同时具备 get secrets。Pod 被准入并调度后,由 kubelet获取并挂载 Secret。
  • ServiceAccount 继承:能够创建 Pod 的主体通常可以在 PodSpec 中指定同命名空间内的其他 ServiceAccount。Kubernetes RBAC 默认没有一个通用的“use serviceaccount”检查来阻止这一点,实际限制往往依赖准入策略。
  • 节点边界突破:如果准入策略允许 privileged、hostPath、hostPID 或 hostNetwork,新 Pod 的影响可能从命名空间扩大到节点。
  • 控制器等价权限:创建 Deployment、DaemonSet、Job 或 CronJob,最终同样能够创建 Pod,不能因为没有直接的 create pods 就排除风险。
  • 已有工作负载修改:对 Deployment 的 patch/update 权限也可能被用于替换镜像、命令或 ServiceAccount。

因此,若 ci-runner 可以创建任意 Pod,而命名空间内存在高权限 ServiceAccount 或敏感 Secret,应至少按“命名空间内权限提升”记录。若同时允许高风险宿主机能力,则应进一步评估节点或集群级影响。

安全验证步骤

第一步是盘点命名空间内可被 Pod 使用的身份及其绑定。测试账号没有读取权限时,可让管理员提供脱敏导出。

# 查看命名空间中的 ServiceAccount,以及是否显式关闭令牌自动挂载
kubectl --context="$CTX" get serviceaccount -n "$NS" \
  -o custom-columns='NAME:.metadata.name,AUTOMOUNT:.automountServiceAccountToken'

# 查看工作负载实际使用的 ServiceAccount
kubectl --context="$CTX" get pods -n "$NS" \
  -o custom-columns='POD:.metadata.name,SA:.spec.serviceAccountName,NODE:.spec.nodeName'

# 查看 Pod Security Admission 标签
kubectl --context="$CTX" get namespace "$NS" --show-labels

# 补充检查命名空间隔离配置
kubectl --context="$CTX" get networkpolicy,resourcequota,limitrange -n "$NS"

第二步使用服务端 dry-run 验证普通 Pod 是否能通过授权和准入。该请求会进入 API Server 的授权与准入流程,但不会持久化对象或启动容器。

kubectl --context="$CTX" apply \
  --as="$SUBJECT" \
  --server-side --dry-run=server -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: rbac-boundary-probe
  namespace: dev
  labels:
    security-test: authorized-dry-run
spec:
  serviceAccountName: ci-runner
  automountServiceAccountToken: false
  restartPolicy: Never
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.9
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        runAsNonRoot: true
        capabilities:
          drop: ["ALL"]
EOF

第三步验证高风险 PodSpec 是否被准入策略拒绝。仍然只使用服务端 dry-run,不实际创建 Pod。预期结果应是被 Pod Security Admission 或其他策略拒绝。

kubectl --context="$CTX" apply \
  --as="$SUBJECT" \
  --server-side --dry-run=server -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: privileged-boundary-probe
  namespace: dev
  labels:
    security-test: authorized-dry-run
spec:
  restartPolicy: Never
  hostPID: true
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.9
      securityContext:
        privileged: true
      volumeMounts:
        - name: host-root
          mountPath: /host
  volumes:
    - name: host-root
      hostPath:
        path: /
        type: Directory
EOF

如果第二个请求返回 dry-run 创建成功,说明集群没有有效阻断 privileged、hostPID 或根目录 hostPath。此时无需实际启动容器,服务端接受结果、RBAC 配置和审计日志已经足以证明控制缺失。

对于 Secret 间接访问,不建议引用真实 Secret 做验证。更稳妥的方式是在隔离测试命名空间中,由资产方创建仅包含随机值的 canary Secret,再让测试 Pod 挂载该对象。验证完成后立即删除测试资源。生产命名空间内如果已经存在敏感 Secret,通常结合“可创建任意 Pod”和“缺少限制 Secret 引用的准入策略”即可形成有效风险结论。

日志留痕与证据固定

建议保留测试时间、集群上下文、主体、命名空间、完整命令、服务端返回码和 Kubernetes Audit ID。不要只保存终端截图,因为截图通常缺少请求身份和对象信息。

如果能够访问控制平面审计日志,可按测试主体过滤:

jq -r '
  select(.user.username == "system:serviceaccount:dev:ci-runner") |
  select(.objectRef.namespace == "dev") |
  [
    .requestReceivedTimestamp,
    .auditID,
    .verb,
    .objectRef.resource,
    (.objectRef.subresource // "-"),
    (.objectRef.name // "-"),
    (.responseStatus.code // 0)
  ] | @tsv
' /var/log/kubernetes/audit.log

审计策略中,Pod 创建和 RBAC 变更可以记录 RequestResponse,但 Secret 资源应避免记录请求体和响应体,否则审计日志本身会成为敏感数据源。可使用如下原则配置:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets"]
  - level: RequestResponse
    verbs: ["create", "update", "patch", "delete"]
    resources:
      - group: ""
        resources: ["pods", "serviceaccounts"]
      - group: "apps"
        resources: ["deployments", "daemonsets", "statefulsets"]
      - group: "batch"
        resources: ["jobs", "cronjobs"]
      - group: "rbac.authorization.k8s.io"
        resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
  - level: Metadata

风险定级参考

  • 可创建 Pod,但只能使用低权限 ServiceAccount,且 enforced 级别为 restricted,同时禁止 hostPath、特权模式和危险网络能力:通常属于命名空间工作负载控制风险,仍需结合 Secret 和业务数据评估。
  • 可创建 Pod,并可指定绑定了更高权限的 ServiceAccount:属于明确的权限提升路径。
  • 可创建 Pod,且能引用命名空间内生产 Secret:可按敏感信息间接访问处理,即使主体自身没有 get secrets。
  • 可创建 privileged、hostPID 或根目录 hostPath Pod:可能扩大到节点权限,应按高危处理。
  • 可创建 DaemonSet 且缺少有效准入限制:影响可能覆盖多个节点,通常比单 Pod 权限更严重。

修复建议

  • 不要把通用的 create pods 授予 CI 身份。优先让 CI 更新受控 Deployment 的指定字段,或通过独立部署控制器完成发布。
  • 需要注意,RBAC 的 resourceNames 不能可靠约束 create 请求,因为对象授权检查阶段无法按既有资源名限制任意新建对象。不要用它解决“只能创建固定名称 Pod”的问题。
  • 为业务、部署系统和运维任务拆分 ServiceAccount,避免多个工作负载共用高权限身份。
  • 对不需要访问 Kubernetes API 的工作负载设置 automountServiceAccountToken: false。这只能减少令牌暴露,不能替代 RBAC 和准入控制。
  • 在生产命名空间启用并强制执行 Pod Security Admission 的 restricted 策略,同时设置 audit 和 warn 标签观察兼容性。
  • 使用 Kyverno 或 Gatekeeper 限制可选的 serviceAccountName,并拒绝 privileged、hostPath、hostPID、hostIPC、危险 capabilities 和不受控 hostNetwork。
  • 通过 NetworkPolicy 限制工作负载访问 API Server、云元数据地址和非必要外部网络。注意 NetworkPolicy 不是身份授权的替代品。
  • 如果确认已经存在可利用窗口,应检查该主体创建或修改过的 Pod、Job、Deployment 和 DaemonSet,并轮换可能被挂载的业务凭据。仅删除 RoleBinding 不能消除已泄露凭据的影响。
# 一个常见的命名空间基线示例,应用前应先评估现有工作负载兼容性
kubectl label namespace dev \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted \
  --overwrite

总结

在 Kubernetes 权限验证中,“不能直接读取 Secret”不等于无法获得 Secret。只要主体能够创建或修改 Pod,就必须继续检查 Secret 引用、ServiceAccount 选择、控制器权限和宿主机能力。实际测试应优先采用 impersonation、只读查询和服务端 dry-run,并通过审计日志固定证据。修复重点不是简单删除一个 verb,而是同时收紧 RBAC、ServiceAccount 使用范围、Pod 准入策略和网络出口。

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

是否构成权限提升,关键不是“能创建 Pod”本身,而是这个 Pod 能否获得当前 ServiceAccount 原本没有的身份、数据或节点权限。建议按下面几条验证。

1. 先确认实际授权范围和所在 namespace

NS=test
SA=app-sa

kubectl auth can-i create pods \
  --as=system:serviceaccount:${NS}:${SA} -n ${NS}

kubectl auth can-i --list \
  --as=system:serviceaccount:${NS}:${SA} -n ${NS}

kubectl get rolebinding,clusterrolebinding -A -o wide | grep -E "${SA}|system:serviceaccounts:${NS}"

注意是否同时具有 pods/exec、pods/attach、pods/log、secrets、rolebindings 等权限。单纯能创建但不能 exec,并不代表无法利用,Pod 仍可主动向外发送数据或访问 API。

2. 检查能否让 Pod 使用 namespace 内更高权限的 ServiceAccount

Pod 创建者通常可以在 spec.serviceAccountName 中指定同 namespace 的其他 ServiceAccount,并不要求其本身具备 impersonate 权限。如果目标 SA 权限更高,且没有准入策略限制,这通常可以认定为权限提升。

kubectl get sa -n ${NS}

kubectl auth can-i --list \
  --as=system:serviceaccount:${NS}:target-sa -n ${NS}

可先用服务端 dry-run 验证准入链是否允许,不会实际落地 Pod:

kubectl create --dry-run=server -f - \
  --as=system:serviceaccount:${NS}:${SA} <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: sa-escalation-check
  namespace: test
spec:
  serviceAccountName: target-sa
  restartPolicy: Never
  containers:
  - name: check
    image: registry.k8s.io/pause:3.10
EOF

如果允许创建,并且 target-sa 比原 SA 权限更高,例如可读取 Secret、修改工作负载或访问其他 namespace,就已经存在清晰的提权路径。实际验证时不必读取敏感数据,证明目标身份具有额外的 can-i 权限即可。

3. 检查 namespace 内的 Secret、ConfigMap 和 PVC 暴露

即使原 SA 没有 get secrets,Pod 创建权限也可能允许其把同 namespace 的 Secret 作为 volume 或环境变量挂载,再通过容器读取。PVC、ConfigMap 同理。因此要检查准入策略是否禁止任意引用:

kubectl get secrets,configmaps,pvc -n ${NS}
kubectl get validatingadmissionpolicies,validatingwebhookconfigurations
kubectl get clusterpolicy,policy -A 2>/dev/null

一个无敏感内容的 dry-run 示例:

kubectl create --dry-run=server -f - \
  --as=system:serviceaccount:${NS}:${SA} <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: secret-mount-check
  namespace: test
spec:
  restartPolicy: Never
  containers:
  - name: check
    image: registry.k8s.io/pause:3.10
    volumeMounts:
    - name: candidate
      mountPath: /mnt/check
      readOnly: true
  volumes:
  - name: candidate
    secret:
      secretName: test-secret
EOF

4. 检查是否可以扩大到节点级权限

重点看准入控制是否允许以下字段:

  • privileged: true 或危险 capabilities;
  • hostPath,尤其是 /、容器运行时 socket、kubelet 目录;
  • hostPID、hostNetwork、hostIPC;
  • 可控的 nodeName、nodeSelector、tolerations,能否调度到控制平面或敏感节点;
  • 可写 PVC 是否被其他高权限工作负载使用。
kubectl get ns ${NS} --show-labels
kubectl get ns ${NS} -o jsonpath='{.metadata.labels}' | jq

重点确认 Pod Security Admission 标签是否为 restricted,以及是否有 Gatekeeper/Kyverno 等额外策略。RBAC 显示可以创建 Pod,只说明通过了授权层;最终能否提交危险 Pod,还取决于准入控制。

结论上可以这样定级:

  • 只能创建受限 Pod,不能选择其他 SA、挂载敏感对象或使用危险主机配置:更偏向“工作负载创建/资源滥用权限”,不一定构成权限提升。
  • 能使用权限更高的 SA,或读取原身份无权访问的 Secret/PVC:属于 namespace 范围的权限提升。
  • 能创建 privileged、hostPath、hostPID 等 Pod 并影响节点:通常可上升为节点权限,影响同节点其他工作负载,风险明显更高。

修复时不能只移除 pods/exec。更有效的是收紧 create pods,为可创建的 Pod 限定 ServiceAccount、Secret/PVC 引用和安全上下文,同时在 namespace 启用 restricted 级别的 Pod Security Admission,并避免同一 namespace 混放高权限 SA 与低信任 Pod 创建者。

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

最近浏览 0

  • 没有会员查看此页面。