Detect gRPC services with reflection API enabled that expose internal service definitions, methods, and message schemas to unauthenticated clients.
Executive Summary
The gRPC Server Reflection API is a powerful development tool that allows clients to discover service definitions, methods, and message types at runtime — without requiring .proto files. When left enabled in production, it becomes a free reconnaissance interface: attackers can enumerate every service your application exposes, understand its internal architecture, and craft targeted attacks against specific endpoints.
The result? Full API surface mapping without authentication. An attacker with network access to a gRPC port can list all registered services, identify sensitive internal endpoints (admin, billing, auth), and use tools like grpcurl to interact with them directly.
Key Insight: Detecting this exposure requires establishing an actual gRPC connection, sending a reflection
ListServicesRPC, and semantically analyzing the response — none of which is achievable with YAML pattern matching or simple banner grabs. CERT-X-GEN's Go template handles the full gRPC wire protocol natively.
Quick Stats
| Metric | Value |
|---|---|
| CVSS Score | 5.3 (Medium) — enumeration only; escalates with exposed sensitive services |
| CWE | CWE-200 (Information Exposure), CWE-538 (Insertion of Sensitive Information) |
| Common Ports | 9090, 50051, 8080, 443 (gRPC) |
| Detection Complexity | Medium (requires gRPC protocol handling) |
| Exploitation Difficulty | Low (grpcurl one-liner once port is reachable) |
Run this check with CERT-X-GEN — the polyglot security scanner that executes templates in real programming languages. Get the templates | Install CERT-X-GEN
Template source:
grpc-reflection-abuse.go
Understanding the Vulnerability
What is gRPC Reflection?
gRPC Reflection is a standard gRPC service (grpc.reflection.v1alpha.ServerReflection) that servers can register alongside their business services. Once registered, any client can query it to discover:
- All registered service names
- Method signatures for each service
- Full protobuf message schemas (field names, types, nesting)
- Streaming vs. unary method types
This is invaluable during development — tools like grpcurl and grpcui rely on it. In production, it is an information disclosure vulnerability.
The Attack Surface
| Exposure Level | What Attacker Learns | Severity |
|---|---|---|
| Reflection enabled, few services | Service names, basic API structure | Medium |
| Reflection enabled, many services | Full microservice architecture map | High |
| Sensitive service names exposed | Internal services (admin, auth, payment, billing) | High |
| No authentication on reflection | Everything above, zero credentials needed | High |
How the Attack Works
┌─────────────────────────────────────────────────────────────────┐
│ gRPC REFLECTION ABUSE ATTACK FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Attacker discovers open gRPC port (50051, 9090, etc.) │
│ ↓ │
│ 2. Attacker sends ListServices reflection RPC │
│ ↓ │
│ 3. Server returns all registered service names │
│ e.g., myapp.AdminService, myapp.PaymentService │
│ ↓ │
│ 4. Attacker queries FileContainingSymbol for each service │
│ ↓ │
│ 5. Server returns full .proto definitions: │
│ methods, message types, field names, enums │
│ ↓ │
│ 6. Attacker now has complete API blueprint │
│ Can craft targeted calls to any method │
│ │
└─────────────────────────────────────────────────────────────────┘Why This Happens
- Copy-paste from tutorials: Most gRPC quickstarts include
reflection.Register(grpcServer)— developers copy it and never remove it - No runtime cost: Unlike debug endpoints, reflection has negligible overhead, so there is no performance incentive to disable it
- Invisible exposure: There is no HTTP path or banner to scan — you only find it by speaking gRPC
- Framework defaults: Some frameworks (e.g., grpc-go health check examples) register reflection alongside health services
Why Traditional Scanners Fall Short
| Capability | YAML Scanner | CERT-X-GEN (Go) |
|---|---|---|
| gRPC wire protocol | ❌ Cannot speak HTTP/2 + gRPC | ✅ Native google.golang.org/grpc |
| Reflection RPC call | ❌ No protobuf serialization | ✅ Full ServerReflectionInfo stream |
| Service enumeration | ❌ Cannot parse reflection responses | ✅ Iterates and classifies services |
| Sensitive name detection | ❌ No semantic analysis | ✅ Keyword matching against service names |
| Severity classification | ❌ Static severity | ✅ Dynamic: info → medium → high based on findings |
The CERT-X-GEN Approach
The template implements a multi-stage detection strategy:
Stage 1 — Connection: Establishes a gRPC connection to the target with configurable timeout. Uses insecure credentials (plaintext) since exposed reflection services rarely use mTLS.
Stage 2 — Reflection Query: Creates a ServerReflectionClient and opens a bidirectional stream. Sends a ListServices request to enumerate all registered services.
Stage 3 — Service Analysis: Filters out the reflection service itself, then classifies each service name against a sensitive keyword list (admin, internal, payment, credential, auth, secret, etc.).
Stage 4 — Severity Assignment: Dynamically assigns severity based on what was found:
| Condition | Severity |
|---|---|
| Reflection not available | Info |
| Reflection enabled, no services | Low |
| ≤2 services, no sensitive names | Medium |
| Multiple services exposed | High |
| Sensitive service names found | High |
Key Template Code: Service Enumeration
// Connect and query reflection
client := grpc_reflection_v1alpha.NewServerReflectionClient(conn)
stream, err := client.ServerReflectionInfo(ctx)
// Request all services
stream.Send(&grpc_reflection_v1alpha.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1alpha.ServerReflectionRequest_ListServices{
ListServices: "*",
},
})
// Parse response
resp, _ := stream.Recv()
for _, service := range resp.GetListServicesResponse().Service {
if isSensitiveServiceName(service.Name) {
result.HasSensitiveNames = true
result.SensitiveServices = append(result.SensitiveServices, service.Name)
}
}Key Template Code: Sensitive Name Detection
func isSensitiveServiceName(serviceName string) bool {
sensitiveKeywords := []string{
"admin", "internal", "private", "debug", "test",
"management", "auth", "user", "account", "payment",
"billing", "credential", "secret", "token", "key",
}
lowerName := strings.ToLower(serviceName)
for _, keyword := range sensitiveKeywords {
if strings.Contains(lowerName, keyword) {
return true
}
}
return false
}Usage Guide
Running the Template
# Basic scan
cxg scan --scope target.com:50051 --templates grpc-reflection-abuse.go
# Multiple ports
cxg scan --scope target.com:9090 --templates grpc-reflection-abuse.go
cxg scan --scope target.com:50051 --templates grpc-reflection-abuse.go
# With extended timeout for slow networks
cxg scan --scope target.com:50051 --templates grpc-reflection-abuse.go --timeout 30s
# JSON output for pipeline integration
cxg scan --scope target.com:50051 --templates grpc-reflection-abuse.go --jsonManual Verification with grpcurl
# Quick check — if this returns service names, reflection is exposed
grpcurl -plaintext target.com:50051 list
# Describe a specific service
grpcurl -plaintext target.com:50051 describe myapp.AdminService
# Should return error if properly secured
grpcurl -plaintext target.com:50051 list 2>&1 | grep -i "unimplemented\|unknown service"Expected Output
Vulnerable system (sensitive services exposed):
{
"findings": [
{
"target": "target.com:50051",
"template_id": "grpc-reflection-abuse",
"severity": "high",
"confidence": 95,
"title": "gRPC Reflection Exposes Sensitive Services",
"description": "Reflection API enabled and exposes 8 service(s), including 3 with sensitive names.",
"evidence": {
"reflection_enabled": true,
"service_count": 8,
"services": [
"myapp.UserService",
"myapp.AdminService",
"myapp.PaymentService",
"myapp.OrderService",
"myapp.InternalMetrics",
"myapp.HealthCheck",
"myapp.ProductCatalog",
"myapp.NotificationService"
],
"has_sensitive_names": true,
"sensitive_services": [
"myapp.AdminService",
"myapp.PaymentService",
"myapp.InternalMetrics"
]
}
}
]
}Secure system (reflection disabled):
{
"findings": [
{
"target": "target.com:50051",
"template_id": "grpc-reflection-abuse",
"severity": "info",
"confidence": 50,
"title": "gRPC Reflection Not Available",
"description": "gRPC reflection service not accessible: connection failed or reflection unimplemented"
}
]
}Defense & Remediation
Immediate Fix
// Remove or conditionally register reflection
// BEFORE (vulnerable):
reflection.Register(grpcServer)
// AFTER (environment-gated):
if os.Getenv("ENABLE_GRPC_REFLECTION") == "true" {
reflection.Register(grpcServer)
}Comprehensive Hardening
| Action | Priority | Effort |
|---|---|---|
Remove reflection.Register() from production builds | Critical | Low |
| Use build tags to exclude reflection in release builds | High | Low |
| Implement mTLS for all gRPC services | High | Medium |
| Add NetworkPolicy to restrict gRPC port access | High | Low |
| Deploy gRPC-aware firewall/proxy (Envoy, Istio) | Medium | Medium |
| Implement per-method authorization interceptors | Medium | Medium |
| Monitor for reflection queries in production logs | Low | Low |
Language-Specific Disabling
Go:
// Use build tags
//go:build dev
reflection.Register(grpcServer)Python:
# Only in development
if settings.DEBUG:
from grpc_reflection.v1alpha import reflection
reflection.enable_server_reflection(SERVICE_NAMES, server)Java:
// Conditional registration
if (System.getenv("ENABLE_REFLECTION") != null) {
ProtoReflectionService.newInstance();
}Verification
# After remediation — should fail with "unimplemented" or connection refused
grpcurl -plaintext your-server.com:50051 list 2>&1 | grep -i "unimplemented\|unknown service\|refused"Extending the Template
Testing Multiple Ports
func scanMultiplePorts(host string) []Finding {
ports := []int{9090, 50051, 8080, 443}
allFindings := []Finding{}
for _, port := range ports {
findings := testVulnerability(host, port, 10)
allFindings = append(allFindings, findings...)
}
return allFindings
}Custom Sensitive Keywords
func loadSensitiveKeywords() []string {
keywords := []string{
"admin", "internal", "private",
// Add organization-specific terms
"mycompany", "confidential", "restricted",
}
if data, err := os.ReadFile("keywords.txt"); err == nil {
lines := strings.Split(string(data), "\n")
keywords = append(keywords, lines...)
}
return keywords
}References
gRPC Reflection Protocol:
Tools:
Security Research:
- CWE-200: Exposure of Sensitive Information
- CWE-538: Insertion of Sensitive Information into Externally-Accessible File
- OWASP API Security Top 10
For questions or contributions, see: cert-x-gen-templates
Need help hardening your environment?
Talk to the Bugb security team directly.