Skip to content

This tool is not affiliated with, endorsed by or sponsored by The Linux Foundation, the Cloud Native Computing Foundation (CNCF) or the Kubernetes project. Kubernetes and K8s are registered trademarks of The Linux Foundation. EKS, GKE, AKS and other names are trademarks of their respective owners.

Kubernetes RBAC Privilege Escalation: Detect It in Logs

Find Kubernetes RBAC privilege escalation in audit logs: cluster-admin bindings, escalate, bind and impersonate verbs, impersonated calls, anonymous grants.

Published on 5 min read

TL;DR. In the audit log, RBAC escalation is a write: a create, update or patch on clusterrolebindings, rolebindings, clusterroles or roles. The dangerous ones bind cluster-admin, grant the escalate, bind or impersonate verbs or * wildcards, or name system:anonymous / system:unauthenticated. You need request bodies (Request level) to see the roleRef and subjects. Also look at the impersonatedUser field, and remember that cloud IAM can grant cluster access without any Kubernetes RBAC object.

A new binding to cluster-admin is the single most reliable sign of a serious Kubernetes intrusion: it is how an attacker turns a borrowed token into a permanent identity. ATT&CK tracks it as T1098.006, Additional Container Cluster Roles.

RBAC in one table

RBAC has four object kinds:

ObjectScopeGrants
RoleNamespaceRules (verbs on resources) inside one namespace
ClusterRoleClusterRules on cluster-scoped resources, or reusable across namespaces
RoleBindingNamespaceA Role or ClusterRole to subjects, inside one namespace
ClusterRoleBindingClusterA ClusterRole to subjects, everywhere

The binding's roleRef names the role; subjects lists users, groups or service accounts. Both are in the request body, so a policy that logs RBAC writes at Metadata tells you "a ClusterRoleBinding named node-health-admin was created" and nothing about what it grants.

The escalation-prevention verbs

Kubernetes blocks the naive escalation: the RBAC documentation explains that you can only create or update a role with permissions you already hold, and only bind a role whose permissions you already hold. Three verbs lift those checks:

  • escalate on roles or clusterroles: write a role with more permissions than you have.
  • bind on roles or clusterroles: bind a role you do not hold, including cluster-admin.
  • impersonate on users, groups or service accounts: act as someone else.

The RBAC good practices list all three as privilege escalation risks, along with * wildcards, which also cover any resource type added in the future. A role that grants any of them to a person or workload is a path to cluster-admin.

Detections

The audit log analyzer checks RBAC writes with these rules:

RuleSeverityLogic
Binding to cluster-admin createdCritical(Cluster)RoleBinding with roleRef.name: cluster-admin
Access granted to anonymous / unauthenticated usersCriticalBinding with subject system:anonymous or system:unauthenticated
Role grants escalate / bind / impersonate or wildcardsHigh(Cluster)Role written with those verbs or * verbs/resources
Request made while impersonating another identityMediumimpersonatedUser present
Anonymous request allowedHighsystem:anonymous allowed beyond health, version and OIDC discovery endpoints
Cluster-wide role binding createdLowAny ClusterRoleBinding written by a non-system identity

The low-severity rule exists because of the logging gap above: when the body is missing, a new ClusterRoleBinding is still worth a look, and the authorization.k8s.io/reason annotation of later requests will name it when it gets used.

Reading a malicious binding

A fictional example, as logged at RequestResponse level:

{
  "verb": "create",
  "user": { "username": "system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard" },
  "sourceIPs": ["203.0.113.45"],
  "userAgent": "kubectl/v1.30.2 (linux/amd64) kubernetes/3968350",
  "objectRef": { "resource": "clusterrolebindings", "name": "node-health-admin",
                 "apiGroup": "rbac.authorization.k8s.io" },
  "requestObject": {
    "kind": "ClusterRoleBinding",
    "metadata": { "name": "node-health-admin" },
    "roleRef": { "apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "cluster-admin" },
    "subjects": [ { "kind": "ServiceAccount", "name": "node-health", "namespace": "kube-system" } ]
  },
  "responseStatus": { "code": 201 }
}

Three things stand out: a service account creating RBAC objects, from an internet address, with kubectl; a name chosen to look like a system component; and a new service account in kube-system as subject. The next step for the attacker is usually a TokenRequest for that service account, which gives them a credential independent of the one they stole first. The fictional walkthrough follows exactly this sequence.

Impersonation

A caller with impersonate permission can send Impersonate-User, Impersonate-Group (and related) headers; the request is then authorised as the impersonated identity. The audit event keeps both: user is who really called, impersonatedUser is who they claimed to be. Always report both, and check who holds impersonate at all: in most clusters that list should be very short.

kubectl --as=<user> and --as-group use these headers; some access proxies and dashboards do too, which is why the analyzer flags impersonation at medium severity and leaves the judgement to you.

Anonymous access

Unless disabled, requests without credentials are authenticated as system:anonymous in group system:unauthenticated, as the authentication documentation describes. Health and discovery endpoints are commonly open. A binding that grants these subjects anything more means anyone who can reach the API server has those rights. Check the system:anonymous glossary entry for the settings that control it.

Escalation paths without an RBAC write

Not every escalation shows up as a binding:

  • Cloud IAM. On EKS, access entries and the aws-auth ConfigMap map IAM principals to Kubernetes identities; associating an admin access policy with an access entry happens in the AWS API (CloudTrail), not in the Kubernetes audit log. GKE grants cluster access through Google Cloud IAM roles, AKS through Azure RBAC role assignments when Azure RBAC for Kubernetes is enabled. Check the cloud control-plane logs for these changes.
  • ClusterRole aggregation. A ClusterRole with labels matching an aggregationRule gets its rules merged into the aggregated role. Adding such a role quietly extends every binding of the aggregated role.
  • Workload creation. Whoever can create pods in a namespace can mount any service account of that namespace and act as it. See secrets and token theft.
  • Certificates. Permission to approve CertificateSigningRequests can mint client certificates for arbitrary identities.
  • Admission webhooks. Control of webhook configurations lets an attacker see or rewrite objects as they are created.

Hunting queries

Raw JSON lines, bindings to cluster-admin:

jq -c 'select((.objectRef.resource == "clusterrolebindings" or .objectRef.resource == "rolebindings")
              and (.verb == "create" or .verb == "update" or .verb == "patch")
              and .requestObject.roleRef.name == "cluster-admin")
       | {t: .requestReceivedTimestamp, user: .user.username, ip: .sourceIPs,
          name: .objectRef.name, subjects: .requestObject.subjects}' audit.log

EKS, CloudWatch Logs Insights, every RBAC write:

fields @timestamp, user.username, verb, objectRef.resource, objectRef.name
| filter @logStream like "kube-apiserver-audit"
| filter objectRef.apiGroup = "rbac.authorization.k8s.io"
| filter verb in ["create", "update", "patch", "delete"]
| sort @timestamp desc

Remediation

  1. Save the offending objects (kubectl get clusterrolebinding <name> -o yaml) as evidence, then delete them.
  2. Delete and recreate any service account that received cluster-admin, so tokens minted for it stop working.
  3. Review every binding to cluster-admin and to roles that can write RBAC, read secrets or exec into pods.
  4. Remove escalate, bind, impersonate and wildcards from roles that do not strictly need them.
  5. Disable anonymous authentication or restrict it to health endpoints.

Related articles

Detect crypto-mining in Kubernetes clusters from audit logs: miner images and arguments, unusual registries, CronJob and DaemonSet persistence, and clean-up.
Spot container escape preparation in Kubernetes audit logs: privileged pods, hostPID, hostNetwork, hostPath of / or runtime sockets, kube-system DaemonSets.
Detect Kubernetes secrets theft and stolen service account tokens in audit logs: cluster-wide lists, read bursts, TokenRequest, public-IP replay, can-i recon.

This tool is not affiliated with, endorsed by or sponsored by The Linux Foundation, the Cloud Native Computing Foundation (CNCF) or the Kubernetes project. Kubernetes and K8s are registered trademarks of The Linux Foundation. EKS, GKE, AKS and other names are trademarks of their respective owners.