Kubernetes RBAC (Role-Based Access Control) is the cluster’s primary authorization system. Properly configured, it enforces least privilege so that a compromised pod, developer account, or CI/CD pipeline cannot escalate to cluster control. In practice, RBAC is frequently misconfigured in ways that create exactly the escalation paths it was designed to prevent.

The misconfigurations are often subtle: a wildcard that looks like a shortcut, a cluster-admin binding on a CI service account for convenience, a default service account left with auto-mounted tokens. This post maps the complete attack surface, demonstrates exploitation techniques, and provides the audit commands and policies to detect and remediate each issue.

The Kubernetes RBAC Model

Kubernetes RBAC has four object types:

  • Role: Grants permissions within a namespace
  • ClusterRole: Grants permissions cluster-wide or to non-namespaced resources
  • RoleBinding: Associates a Role or ClusterRole with subjects (users, groups, service accounts) within a namespace
  • ClusterRoleBinding: Associates a ClusterRole with subjects cluster-wide

A permission is defined as a combination of API groups, resources, and verbs:

1apiVersion: rbac.authorization.k8s.io/v1
2kind: Role
3metadata:
4  namespace: production
5  name: pod-reader
6rules:
7- apiGroups: [""]          # Core API group
8  resources: ["pods"]
9  verbs: ["get", "watch", "list"]

The subject types are:

  • User — external identity (authenticated via OIDC, certificates, etc.)
  • Group — group membership from the identity provider
  • ServiceAccount — in-cluster identity assigned to pods

Common Misconfiguration Patterns

Pattern 1: Wildcard Permissions

1# DANGEROUS: This grants cluster-admin equivalent access
2apiVersion: rbac.authorization.k8s.io/v1
3kind: ClusterRole
4metadata:
5  name: developer-access
6rules:
7- apiGroups: ["*"]
8  resources: ["*"]
9  verbs: ["*"]

Any subject bound to this ClusterRole can read secrets, create pods, modify RBAC itself, and execute commands in any pod — full cluster compromise from a single binding.

Pattern 2: cluster-admin Binding on Service Accounts

 1# Frequently seen in CI/CD pipelines
 2apiVersion: rbac.authorization.k8s.io/v1
 3kind: ClusterRoleBinding
 4metadata:
 5  name: jenkins-admin
 6subjects:
 7- kind: ServiceAccount
 8  name: jenkins
 9  namespace: ci
10roleRef:
11  kind: ClusterRole
12  name: cluster-admin
13  apiGroup: rbac.authorization.k8s.io

If Jenkins is compromised (malicious build, RCE via plugin vulnerability), the attacker inherits cluster-admin access.

Pattern 3: Anonymous Authentication Enabled

Kubernetes API server can be configured with --anonymous-auth=true (the default in older versions), which allows unauthenticated requests to be processed as the system:anonymous user. If RBAC grants any permissions to system:unauthenticated, they are accessible without credentials.

1# Check if anonymous auth is enabled
2kubectl get --raw /api/v1/namespaces --server https://k8s-api:6443 \
3  --insecure-skip-tls-verify 2>&1
4# If response is JSON (not 403), anonymous access is enabled
5
6# Check what anonymous users can do
7kubectl auth can-i --list --as=system:anonymous
8kubectl auth can-i --list --as=system:unauthenticated

Pattern 4: pods/exec Abuse

The pods/exec resource permission is a lateral movement superpower:

1# A developer role that looks reasonable but enables cluster compromise
2rules:
3- apiGroups: [""]
4  resources: ["pods/exec"]
5  verbs: ["create"]

Any subject with this permission can execute commands in pods that have high-privilege service accounts.

Real Incident: Tesla Cryptojacking (2018)

In February 2018, cloud security company RedLock published research revealing that Tesla’s production Kubernetes cluster had been compromised for cryptocurrency mining.

Attack chain:

  1. Attackers used Shodan/masscan to identify Kubernetes dashboards exposed on the internet
  2. Tesla’s Kubernetes dashboard was accessible without authentication at a non-standard port
  3. From the dashboard, attackers browsed Kubernetes secrets and found AWS credentials
  4. AWS credentials provided access to Tesla’s S3 buckets containing telemetry data
  5. Attackers deployed cryptocurrency mining pods (configured to mine Monero to evade detection through pool obfuscation)
  6. Miners were configured with CPU limits to avoid performance alerts and used unlisted mining pool proxies

Technical root causes:

  • Kubernetes dashboard exposed without authentication (--enable-skip-login flag)
  • AWS credentials stored as plaintext in Kubernetes secrets (not encrypted at rest)
  • No network policy restricting dashboard access
  • No pod security controls preventing deployment of arbitrary workloads

The incident was significant not because of data theft (Tesla reported no customer data was accessed) but because it demonstrated that misconfigured Kubernetes clusters were being actively scanned and exploited.

CVE-2018-1002105: API Aggregation Privilege Escalation

This was the most severe Kubernetes CVE to date when disclosed in December 2018.

Vulnerability: The Kubernetes API server’s aggregation layer improperly proxied upgrade requests (WebSocket) to extension API servers. A user with any permission to make requests through an aggregated API could send arbitrary HTTP requests to the backend API server authenticated as the Kubernetes API server itself — which has cluster-admin privileges.

CVSS Score: 9.8 (Critical) Affected versions: All Kubernetes versions before 1.10.11, 1.11.5, 1.12.3

1# Check Kubernetes version for CVE-2018-1002105
2kubectl version --short
3# Vulnerable: Server Version v1.x.y where x < 10, or x=10 and y<11, etc.
4
5# Check if API aggregation is configured (presence of APIService objects)
6kubectl get apiservices | grep -v "Local"
7# Non-local APIServices enable the aggregation vector

Attack Flow: Exploiting RBAC Misconfigurations

Step 1: Enumerate Current Permissions

 1# What can the current user/service account do?
 2kubectl auth can-i --list
 3
 4# Example output showing dangerous permissions:
 5# Resources                                   Non-Resource URLs   Resource Names   Verbs
 6# *.apps                                      []                  []               [*]
 7# pods/exec                                   []                  []               [create]
 8# secrets                                     []                  []               [get list]
 9
10# Check specific permissions
11kubectl auth can-i create pods --namespace production
12kubectl auth can-i get secrets --namespace kube-system
13kubectl auth can-i impersonate users

Step 2: Audit All ClusterRoleBindings for Dangerous Subjects

 1# List all ClusterRoleBindings and their subjects
 2kubectl get clusterrolebindings -o json | python3 -c "
 3import sys, json
 4data = json.load(sys.stdin)
 5for item in data['items']:
 6    role = item['roleRef']['name']
 7    subjects = item.get('subjects', [])
 8    for subj in subjects:
 9        name = subj.get('name', 'unknown')
10        kind = subj.get('kind', 'unknown')
11        ns = subj.get('namespace', 'cluster')
12        print(f'{role:50} {kind:15} {name:40} {ns}')
13" | grep -E "cluster-admin|system:masters"
14
15# Find service accounts with cluster-admin
16kubectl get clusterrolebindings -o json | \
17  python3 -c "
18import sys, json
19data = json.load(sys.stdin)
20for item in data['items']:
21    if item['roleRef']['name'] == 'cluster-admin':
22        for s in item.get('subjects', []):
23            if s['kind'] == 'ServiceAccount':
24                print(f\"RISK: {s.get('namespace','?')}/{s['name']} has cluster-admin\")
25"

Step 3: Identify Secrets Access from Service Accounts

1# Which service accounts can read secrets in kube-system?
2for sa in $(kubectl get sa -n kube-system -o jsonpath='{.items[*].metadata.name}'); do
3  result=$(kubectl auth can-i get secrets \
4    --namespace kube-system \
5    --as=system:serviceaccount:kube-system:$sa 2>/dev/null)
6  if [ "$result" = "yes" ]; then
7    echo "WARNING: $sa can read kube-system secrets"
8  fi
9done

Step 4: Python Script — Enumerate RBAC Misconfigurations via K8s API

  1#!/usr/bin/env python3
  2"""
  3K8s RBAC Misconfiguration Scanner
  4Identifies dangerous RBAC configurations in a Kubernetes cluster.
  5"""
  6
  7from kubernetes import client, config
  8import sys
  9
 10DANGEROUS_VERBS = {"*", "create", "update", "patch", "delete", "escalate", "bind"}
 11SENSITIVE_RESOURCES = {"secrets", "pods/exec", "clusterroles", "clusterrolebindings",
 12                       "serviceaccounts/token", "nodes", "pods/attach"}
 13
 14
 15def load_kube_config():
 16    try:
 17        config.load_incluster_config()
 18    except config.ConfigException:
 19        config.load_kube_config()
 20
 21
 22def check_rules(rules, role_name, binding_name, subjects):
 23    findings = []
 24    for rule in rules or []:
 25        verbs = set(rule.get("verbs", []))
 26        resources = set(rule.get("resources", []))
 27        api_groups = set(rule.get("apiGroups", []))
 28
 29        # Wildcard check
 30        if "*" in verbs and "*" in resources:
 31            findings.append({
 32                "severity": "CRITICAL",
 33                "binding": binding_name,
 34                "role": role_name,
 35                "issue": "Wildcard verb+resource (cluster-admin equivalent)",
 36                "subjects": subjects,
 37            })
 38
 39        # Sensitive resource with dangerous verbs
 40        for resource in resources:
 41            if resource in SENSITIVE_RESOURCES and verbs & DANGEROUS_VERBS:
 42                findings.append({
 43                    "severity": "HIGH",
 44                    "binding": binding_name,
 45                    "role": role_name,
 46                    "issue": f"Dangerous verbs {verbs & DANGEROUS_VERBS} on {resource}",
 47                    "subjects": subjects,
 48                })
 49    return findings
 50
 51
 52def main():
 53    load_kube_config()
 54    rbac = client.RbacAuthorizationV1Api()
 55    findings = []
 56
 57    # Check ClusterRoleBindings
 58    crbs = rbac.list_cluster_role_binding()
 59    roles_cache = {}
 60
 61    for crb in crbs.items:
 62        role_name = crb.role_ref.name
 63        binding_name = crb.metadata.name
 64        subjects = [
 65            f"{s.kind}/{s.namespace or 'cluster'}/{s.name}"
 66            for s in (crb.subjects or [])
 67        ]
 68
 69        # Direct cluster-admin binding
 70        if role_name == "cluster-admin":
 71            for s in (crb.subjects or []):
 72                if s.kind == "ServiceAccount":
 73                    findings.append({
 74                        "severity": "CRITICAL",
 75                        "binding": binding_name,
 76                        "role": "cluster-admin",
 77                        "issue": f"ServiceAccount has cluster-admin",
 78                        "subjects": subjects,
 79                    })
 80
 81        # Fetch and check ClusterRole rules
 82        if role_name not in roles_cache:
 83            try:
 84                cr = rbac.read_cluster_role(role_name)
 85                roles_cache[role_name] = cr.rules or []
 86            except Exception:
 87                roles_cache[role_name] = []
 88
 89        findings.extend(check_rules(roles_cache[role_name], role_name, binding_name, subjects))
 90
 91    # Check anonymous auth bindings
 92    for crb in crbs.items:
 93        for s in (crb.subjects or []):
 94            if s.name in ("system:anonymous", "system:unauthenticated"):
 95                findings.append({
 96                    "severity": "CRITICAL",
 97                    "binding": crb.metadata.name,
 98                    "role": crb.role_ref.name,
 99                    "issue": "Anonymous/unauthenticated access granted",
100                    "subjects": [f"{s.kind}/{s.name}"],
101                })
102
103    # Report
104    if not findings:
105        print("[OK] No critical RBAC misconfigurations found.")
106        return
107
108    print(f"[!] Found {len(findings)} RBAC misconfiguration(s):\n")
109    for f in findings:
110        print(f"  [{f['severity']}] {f['binding']} -> {f['role']}")
111        print(f"         Issue: {f['issue']}")
112        print(f"         Subjects: {', '.join(f['subjects'])}")
113        print()
114
115
116if __name__ == "__main__":
117    main()

Step 5: Exploit pods/exec to Steal Service Account Token

 1# Find a high-value pod (e.g., one running with an admin service account)
 2kubectl get pods --all-namespaces -o json | python3 -c "
 3import sys, json
 4data = json.load(sys.stdin)
 5for item in data['items']:
 6    sa = item['spec'].get('serviceAccountName', 'default')
 7    ns = item['metadata']['namespace']
 8    name = item['metadata']['name']
 9    if sa not in ('default', 'none'):
10        print(f'{ns}/{name} -> serviceAccount: {sa}')
11"
12
13# Execute into a high-privilege pod and read its token
14kubectl exec -n monitoring prometheus-0 -- \
15  cat /var/run/secrets/kubernetes.io/serviceaccount/token
16
17# Use the stolen token to query the API
18export STOLEN_TOKEN="eyJhbGciOiJSUzI1NiIs..."
19kubectl get secrets --all-namespaces \
20  --token=$STOLEN_TOKEN \
21  --server=https://k8s-api:6443 \
22  --insecure-skip-tls-verify

Detection

Falco Rules for kubectl exec and Token Access

 1# /etc/falco/rules.d/rbac-abuse.yaml
 2
 3- rule: K8s Exec into Pod
 4  desc: Detect kubectl exec used to access a container
 5  condition: >
 6    ka.target.resource = "pods/exec"
 7    and ka.verb = "create"
 8  output: >
 9    Pod exec attempt (user=%ka.user.name pod=%ka.target.name
10    namespace=%ka.target.namespace)
11  priority: WARNING
12  source: k8s_audit
13  tags: [k8s, rbac, T1552.007]
14
15- rule: Service Account Token Mounted and Read
16  desc: Detect reading of mounted service account token
17  condition: >
18    open_read
19    and container
20    and fd.name startswith "/var/run/secrets/kubernetes.io/serviceaccount/token"
21    and not proc.name in (legitimate_token_readers)
22  output: >
23    Service account token read (user=%user.name command=%proc.cmdline
24    container=%container.name)
25  priority: NOTICE

Kubernetes Audit Log Analysis

 1# Stream audit logs for RBAC-sensitive API calls
 2# (Assumes audit log is written to file by kube-apiserver)
 3tail -f /var/log/kubernetes/audit.log | \
 4  python3 -c "
 5import sys, json
 6for line in sys.stdin:
 7    try:
 8        ev = json.loads(line)
 9        sensitive_resources = ['secrets', 'clusterrolebindings', 'pods/exec']
10        resource = ev.get('objectRef', {}).get('resource', '')
11        subresource = ev.get('objectRef', {}).get('subresource', '')
12        full_resource = f'{resource}/{subresource}' if subresource else resource
13        if full_resource in sensitive_resources and ev.get('verb') in ['get', 'create', 'list']:
14            user = ev.get('user', {}).get('username', 'unknown')
15            ns = ev.get('objectRef', {}).get('namespace', 'cluster')
16            name = ev.get('objectRef', {}).get('name', 'unknown')
17            print(f'[AUDIT] {ev[\"verb\"]} {full_resource} {ns}/{name} by {user}')
18    except:
19        pass
20"
21
22# Using rbac-tool for misconfiguration analysis
23# Install: kubectl krew install rbac-tool
24kubectl rbac-tool policy-rules -e '' | grep -E "\*|\bexec\b|\bsecrets\b"

Defense and Hardening

Control 1: Disable Anonymous Authentication

1# In kube-apiserver configuration (kubeadm cluster)
2# Edit /etc/kubernetes/manifests/kube-apiserver.yaml
3# Add or verify:
4#   - --anonymous-auth=false
5
6# Verify:
7kubectl get pods -n kube-system kube-apiserver-$(hostname) -o yaml | \
8  grep anonymous-auth

Control 2: Namespace Isolation with Network Policies

 1# Deny all ingress/egress by default in each namespace
 2apiVersion: networking.k8s.io/v1
 3kind: NetworkPolicy
 4metadata:
 5  name: default-deny-all
 6  namespace: production
 7spec:
 8  podSelector: {}
 9  policyTypes:
10  - Ingress
11  - Egress

Control 3: Restrict Default Service Account Token Auto-Mount

1# Disable auto-mounting for the default service account
2apiVersion: v1
3kind: ServiceAccount
4metadata:
5  name: default
6  namespace: production
7automountServiceAccountToken: false

Control 4: Pod Security Admission

1# Apply restricted Pod Security Standard to namespaces
2apiVersion: v1
3kind: Namespace
4metadata:
5  name: production
6  labels:
7    pod-security.kubernetes.io/enforce: restricted
8    pod-security.kubernetes.io/audit: restricted
9    pod-security.kubernetes.io/warn: restricted

Control 5: Minimal RBAC — Principle of Least Privilege

 1# Instead of wildcard, grant only what's needed
 2apiVersion: rbac.authorization.k8s.io/v1
 3kind: Role
 4metadata:
 5  namespace: production
 6  name: app-deployer
 7rules:
 8- apiGroups: ["apps"]
 9  resources: ["deployments"]
10  verbs: ["get", "list", "watch", "create", "update", "patch"]
11- apiGroups: [""]
12  resources: ["pods"]
13  verbs: ["get", "list", "watch"]
14# No pods/exec, no secrets, no cluster-level resources

MITRE ATT&CK Mapping

  • T1552.007 — Unsecured Credentials: Container API: Extract credentials from Kubernetes service account tokens or secrets.
  • T1078.001 — Valid Accounts: Default Accounts: Abuse of default service accounts with excessive permissions.
  • T1611 — Escape to Host: Using pods/exec to escape into privileged containers with host access.
  • T1098 — Account Manipulation: Modifying RBAC bindings to maintain or escalate access.

References