$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-grpcHunt 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
| 1 | # HUNT-GRPC — gRPC Security |
| 2 | |
| 3 | ## Crown Jewel Targets |
| 4 | |
| 5 | 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. |
| 6 | |
| 7 | **Highest-value findings:** |
| 8 | - **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). |
| 9 | - **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. |
| 10 | - **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. |
| 11 | - **Plaintext gRPC** — gRPC h2c (cleartext HTTP/2) on a non-standard port → credential/metadata interception. |
| 12 | - **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.** |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## Phase 1 — Fingerprint & Port Discovery |
| 17 | |
| 18 | ```bash |
| 19 | # Common gRPC ports (50051 native; 443/8443 via TLS+ALPN h2; 9090/8080 h2c) |
| 20 | nmap -sV -p 50051,50052,443,9090,8080,8443,6565,9000 $TARGET 2>/dev/null | grep open |
| 21 | |
| 22 | # ALPN must negotiate h2 — gRPC cannot run on HTTP/1.1 |
| 23 | echo | openssl s_client -alpn h2 -connect $TARGET:443 2>/dev/null | grep -i "ALPN.*h2" |
| 24 | |
| 25 | # Native-gRPC fingerprint: an HTTP/2 POST to a bogus method returns a grpc-status |
| 26 | # trailer (12 = UNIMPLEMENTED) even when the path is wrong — strong signal it's gRPC. |
| 27 | curl -s --http2-prior-knowledge -X POST "http://$TARGET:9090/x.Y/Z" \ |
| 28 | -H "content-type: application/grpc" -o /dev/null -D - | grep -i grpc-status |
| 29 | |
| 30 | # TLS-fronted h2 (port 443): look for grpc-status trailer / grpc content-type |
| 31 | curl -s --http2 -X POST "https://$TARGET/grpc.health.v1.Health/Check" \ |
| 32 | -H "content-type: application/grpc-web+proto" -o /dev/null -D - | grep -i "grpc-status\|content-type" |
| 33 | ``` |
| 34 | |
| 35 | `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. |
| 36 | |
| 37 | --- |
| 38 | |
| 39 | ## Phase 2 — Service Enumeration via Reflection |
| 40 | |
| 41 | ```bash |
| 42 | brew install grpcurl # or: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest |
| 43 | |
| 44 | # List services — -plaintext for h2c, -insecure for self-signed TLS, plain for valid TLS |
| 45 | grpcurl -plaintext $TARGET:50051 list |
| 46 | grpcurl -insecure $TARGET:443 list |
| 47 | |
| 48 | # Typical output when reflection is on: |
| 49 | # grpc.reflection.v1.ServerReflection |
| 50 | # grpc.health.v1.Health |
| 51 | # user.UserService |
| 52 | # admin.AdminService |
| 53 | # payment.PaymentService |
| 54 | |
| 55 | # List + describe every method of each service |
| 56 | grpcurl -plaintext $TARGET:50051 list admin.AdminService |
| 57 | grpcurl -plaintext $TARGET:50051 describe admin.AdminService.DeleteUser |
| 58 | grpcurl -plaintext $TARGET:50051 describe .admin.DeleteUserRequest # message schema |
| 59 | |
| 60 | # Dump the whole catalog to triage interesting surfaces |
| 61 | for SVC in $(grpcurl -plaintext $TARGET:50051 list); do |
| 62 | echo "== $SVC =="; grpcurl -plaintext $TARGET:50051 list "$SVC" |
| 63 | done | tee grpc-catalog.txt |
| 64 | grep -iE 'admin|internal|debug|secret|impersonate|exec|migrate|reset|delete' grpc-catalog.txt |
| 65 | ``` |
| 66 | |
| 67 | **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. |
| 68 | |
| 69 | --- |
| 70 | |
| 71 | ## Phase 3 — Call Methods Without Authentication (authz testing) |
| 72 | |
| 73 | ```bash |
| 74 | # Baseline: call a sensitive method with NO auth metadata |
| 75 | grpcurl -plaintext $TARGET:50051 -d '{}' admin.AdminService/ListUsers |
| 76 | |
| 77 | # IDOR across an enumerable id field |
| 78 | for ID in 1 2 3 100 1000 1001; do |
| 79 | echo "id=$ID"; grpcurl -plaintext $TARGET:50051 \ |
| 80 | -d "{\"user_id\": $ID}" u |