-
Notifications
You must be signed in to change notification settings - Fork 6
gRPC API Discovery
This tutorial walks through discovering a gRPC API with Vespasian and reconstructing its .proto schema via the Server Reflection Protocol. It covers both the reflection-enabled path (you get a full .proto) and the reflection-disabled / auth-gated path (you get a structured finding instead of a schema).
gRPC is opt-in. Unlike REST, GraphQL, and WSDL, gRPC is not auto-detected — its binary HTTP/2 framing doesn't carry the signals the auto-detector keys on. You must select it explicitly with
--api-type grpc, or use the dedicatedvespasian probe grpc reflectioncommand.
- A built
vespasianbinary (make build, orgo install github.com/praetorian-inc/vespasian/cmd/vespasian@latest). - A gRPC target. This tutorial uses the bundled test server in
test/grpc-server/, which exposesUserService,OrderService, andAccountService(including a server-streamingListUsers) with reflection enabled.
Start the test server in one terminal:
make -C test/grpc-server run
# listening on localhost:50051 with Server Reflection registeredSanity-check it with grpcurl:
grpcurl -plaintext localhost:50051 list
# grpc.reflection.v1.ServerReflection
# lab.v1.AccountService
# lab.v1.OrderService
# lab.v1.UserServiceWhen a server has reflection registered, Vespasian asks it to describe its own services, methods, and message types, walks the transitive FileDescriptorProto import graph, and renders proto3 source.
The quickest path is the standalone probe, which doesn't need a capture file:
vespasian probe grpc reflection http://localhost:50051 \
--dangerous-allow-private \
-o api.proto -v-
--dangerous-allow-privateis required here becauselocalhostis a private address and SSRF protection blocks private targets by default. Never use this flag against production targets. - The port (
:50051) is required — gRPC commonly runs on non-standard ports. - For a TLS endpoint, use an
https://(orgrpcs://) URL; the port defaults to443.
Verbose output:
probing http://localhost:50051 for gRPC reflection
discovered 3 service(s)
api.proto now contains the reconstructed schema, for example:
syntax = "proto3";
package lab.v1;
service UserService {
rpc GetUser ( .lab.v1.GetUserRequest ) returns ( .lab.v1.User );
rpc ListUsers ( .lab.v1.ListUsersRequest ) returns ( stream .lab.v1.User );
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
string id = 1;
}
// ... OrderService, AccountService, and their messages follow(Exact formatting is whatever protoprint emits; the server-streaming ListUsers is rendered with stream on its return type.)
Notes on the output:
- It's deterministic: files are sorted and elements within each file are sorted, so re-running produces identical output.
- The well-known
google/protobuf/*files are omitted — any consumer (protoc,buf generate) already has them. - The reflection service itself (
grpc.reflection.v1.ServerReflection) is filtered out; only your user-facing services appear.
You can also reach gRPC through the normal two-stage pipeline. Because gRPC isn't auto-detected, pass --api-type grpc:
# One step
vespasian scan https://grpc.example.com:443 --api-type grpc -o api.proto
# Or two stages
vespasian crawl https://grpc.example.com:443 -o capture.json
vespasian generate grpc capture.json -o api.proto
generate grpcrequires reflection descriptors to be present in the capture. gRPC's wire format strips field names, so traffic-only.protoinference is not supported — a capture with no reflection data yields no spec.
Many production servers ship with reflection turned off, or gate it behind authentication. Vespasian handles this gracefully: instead of crashing or returning an empty result, it reports why reflection was unavailable, using the gRPC status code the server returned.
Point the probe at a server without reflection registered:
vespasian probe grpc reflection https://grpc-no-reflection.example.com:443Representative outcomes:
| Server response | gRPC status | What Vespasian reports |
|---|---|---|
| Reflection service not registered | Unimplemented |
reflection not available on <url> (gRPC Unimplemented) |
| Reflection requires credentials | Unauthenticated |
reflection not available on <url> (gRPC Unauthenticated) |
| Caller lacks permission | PermissionDenied |
reflection not available on <url> (gRPC PermissionDenied) |
| Not a gRPC server / ambiguous network error | — |
reflection not available on <url> (no structured reason) |
In the pipeline (scan/generate), the same condition surfaces on the classified endpoint as a GRPCReflectionResult with reflection_enabled: false and a reflection_unavailable_reason field, so it's visible in the capture even though no .proto is produced.
Vespasian's reflection probe does not currently send call credentials or metadata. So a server that requires auth for reflection is detected and reported as Unauthenticated/PermissionDenied — it is not bypassed, and no schema is reconstructed. Credentialed reflection is a possible future enhancement.
Even when you can't reconstruct a schema, the classifier can still flag gRPC endpoints in captured/imported traffic (e.g. a Burp or proxy capture). It scores requests on:
-
application/grpc/application/grpc-web*content-type (strongest signal), -
grpc-status/grpc-messagetrailer headers, - a
POSTto a/<pkg.qualified.Service>/<Method>path,
with content-type + trailers together giving the highest confidence. This tells you a gRPC surface exists and where it is, even if reflection is off — useful for scoping further manual testing.
| Symptom | Cause / fix |
|---|---|
crawl exits with an error and captures nothing against localhost
|
SSRF protection blocked a private seed. Add --dangerous-allow-private (non-production only). |
reflection not available (gRPC Unimplemented) |
The server has reflection disabled. You can't reconstruct the schema via Vespasian; obtain the .proto out-of-band. |
generate grpc produces no output |
The capture has no reflection descriptors. Use vespasian probe grpc reflection against a live server, or scan --api-type grpc. |
| Connection hangs / times out | Wrong port or TLS mismatch. gRPC often uses :50051; use https:///grpcs:// for TLS endpoints. Adjust --timeout if needed. |
- README → API Type Support → gRPC Classification Heuristics and gRPC Server Reflection
- README → CLI Reference →
vespasian probe -
test/grpc-server/README.md— the bundled test target's services and operations
Vespasian is built by Praetorian. Learn more about the Praetorian Guard attack surface management platform. Licensed under Apache 2.0.