Pentesting gRPC-Web

Reading time: 7 minutes

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Quick protocol recap and attack surface

  • Transport: gRPC‑Web speaks a browser‑compatible variant of gRPC over HTTP/1.1 or HTTP/2 via a proxy (Envoy/APISIX/grpcwebproxy/etc.). Only unary and server‑streaming calls are supported.
  • Content-Types you will see:
    • application/grpc-web (binary framing)
    • application/grpc-web-text (base64-encoded framing for HTTP/1.1 streaming)
  • Framing: every message is prefixed with a 5‑byte gRPC header (1‑byte flags + 4‑byte length). In gRPC‑Web, trailers (grpc-status, grpc-message, …) are sent inside the body as a special frame: first byte with MSB set (0x80) followed by a length and a HTTP/1.1‑style header block.
  • Common request headers: x-grpc-web: 1, x-user-agent: grpc-web-javascript/…, grpc-timeout, grpc-encoding. Responses expose grpc-status/grpc-message via trailers/body frames and often via Access-Control-Expose-Headers for browsers.
  • Security‑relevant middleware often present:
    • Envoy grpc_web filter and gRPC‑JSON transcoder (HTTP<->gRPC bridge)
    • Nginx/APISIX gRPC‑Web plugins
    • CORS policies on the proxy

What this means for attackers:

  • You can craft requests by hand (binary or base64 text), or let tooling generate/encode them.
  • CORS mistakes on the proxy can allow cross‑site, authenticated gRPC‑Web calls (similar to classic CORS issues).
  • JSON transcoding bridges may unintentionally expose gRPC methods as unauthenticated HTTP endpoints if routes/auth are misconfigured.

Testing gRPC‑Web from the CLI

Easiest: buf curl (speaks gRPC‑Web natively)

  • List methods via reflection (if enabled):
bash
# list methods (uses reflection)
buf curl --protocol grpcweb https://host.tld --list-methods
  • Call a method with JSON input, auto‑handling gRPC‑Web framing and headers:
bash
buf curl --protocol grpcweb \
  -H 'Origin: https://example.com' \
  -d '{"field":"value"}' \
  https://host.tld/pkg.svc.v1.Service/Method
  • If reflection is disabled, provide a schema/descriptor set with --schema or point to local .proto files. See buf help curl.

Raw with curl (manual headers + framed body)

For binary mode (application/grpc-web), send a framed payload (5‑byte prefix + protobuf message). For text mode, base64‑encode the framed payload.

bash
# Build a protobuf message, then gRPC-frame it (1 flag byte + 4 length + msg)
# Example using protoscope to compose/edit the message and base64 for grpc-web-text
protoscope -s msg.txt | python3 grpc-coder.py --encode --type grpc-web-text | \
  tee body.b64

curl -i https://host.tld/pkg.svc.v1.Service/Method \
  -H 'Content-Type: application/grpc-web-text' \
  -H 'X-Grpc-Web: 1' \
  -H 'X-User-Agent: grpc-web-javascript/0.1' \
  --data-binary @body.b64

Tip: Force base64/text mode with application/grpc-web-text when HTTP/1.1 intermediaries break binary streaming.

Check CORS behavior (preflight + response)

  • Preflight:
bash
curl -i -X OPTIONS https://host.tld/pkg.svc.v1.Service/Method \
  -H 'Origin: https://evil.tld' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type,x-grpc-web,x-user-agent,grpc-timeout'
  • A vulnerable setup often reflects arbitrary Origin and sends Access-Control-Allow-Credentials: true, allowing cross‑site authenticated calls. Also check Access-Control-Expose-Headers includes grpc-status, grpc-message (many deployments expose these for client libs).

For generic techniques to abuse CORS, check CORS - Misconfigurations & Bypass.

Manipulating gRPC‑Web payloads

gRPC‑Web uses Content-Type: application/grpc-web-text as a base64‑wrapped gRPC frame stream for browser compatibility. You can decode/modify/encode frames to tamper with fields, flip flags, or inject payloads.

Use the gprc-coder tool (and its Burp extension) to speed up round‑trips.

Manual with gGRPC Coder Tool

  1. Decode the payload:
bash
echo "AAAAABYSC0FtaW4gTmFzaXJpGDY6BVhlbm9u" | python3 grpc-coder.py --decode --type grpc-web-text | protoscope > out.txt
  1. Edit the content of decoded payload
nano out.txt
2: {"Amin Nasiri Xenon GRPC"}
3: 54
7: {"<script>alert(origin)</script>"}
  1. Encode the new payload
bash
protoscope -s out.txt | python3 grpc-coder.py --encode --type grpc-web-text
  1. Use output in Burp interceptor:
AAAAADoSFkFtaW4gTmFzaXJpIFhlbm9uIEdSUEMYNjoePHNjcmlwdD5hbGVydChvcmlnaW4pPC9zY3JpcHQ+

Manual with gRPC‑Web Coder Burp Suite Extension

You can use gRPC‑Web Coder Burp Suite Extension in gRPC‑Web Pentest Suite which is easier. You can read the installation and usage instruction in its repo.

Analysing gRPC‑Web JavaScript files

Web apps using gRPC‑Web ship at least one generated JS/TS bundle. Reverse them to extract services, methods, and message shapes.

  • Try using gRPC-Scan to parse bundles.
  • Look for method paths like /./, message field numbers/types, and custom interceptors that add auth headers.
  1. Download the JavaScript gRPC‑Web file
  2. Scan it with grpc-scan.py:
bash
python3 grpc-scan.py --file main.js
  1. Analyse output and test the new endpoints and new services:
Output:
Found Endpoints:
  /grpc.gateway.testing.EchoService/Echo
  /grpc.gateway.testing.EchoService/EchoAbort
  /grpc.gateway.testing.EchoService/NoOp
  /grpc.gateway.testing.EchoService/ServerStreamingEcho
  /grpc.gateway.testing.EchoService/ServerStreamingEchoAbort

Found Messages:

grpc.gateway.testing.EchoRequest:
+------------+--------------------+--------------+
| Field Name |     Field Type     | Field Number |
+============+====================+==============+
| Message    | Proto3StringField  | 1            |
+------------+--------------------+--------------+
| Name       | Proto3StringField  | 2            |
+------------+--------------------+--------------+
| Age        | Proto3IntField     | 3            |
+------------+--------------------+--------------+
| IsAdmin    | Proto3BooleanField | 4            |
+------------+--------------------+--------------+
| Weight     | Proto3FloatField   | 5            |
+------------+--------------------+--------------+
| Test       | Proto3StringField  | 6            |
+------------+--------------------+--------------+
| Test2      | Proto3StringField  | 7            |
+------------+--------------------+--------------+
| Test3      | Proto3StringField  | 16           |
+------------+--------------------+--------------+
| Test4      | Proto3StringField  | 20           |
+------------+--------------------+--------------+

grpc.gateway.testing.EchoResponse:
+--------------+--------------------+--------------+
|  Field Name  |     Field Type     | Field Number |
+==============+====================+==============+
| Message      | Proto3StringField  | 1            |
+--------------+--------------------+--------------+
| Name         | Proto3StringField  | 2            |
+--------------+--------------------+--------------+
| Age          | Proto3IntField     | 3            |
+--------------+--------------------+--------------+
| IsAdmin      | Proto3BooleanField | 4            |
+--------------+--------------------+--------------+
| Weight       | Proto3FloatField   | 5            |
+--------------+--------------------+--------------+
| Test         | Proto3StringField  | 6            |
+--------------+--------------------+--------------+
| Test2        | Proto3StringField  | 7            |
+--------------+--------------------+--------------+
| Test3        | Proto3StringField  | 16           |
+--------------+--------------------+--------------+
| Test4        | Proto3StringField  | 20           |
+--------------+--------------------+--------------+
| MessageCount | Proto3IntField     | 8            |
+--------------+--------------------+--------------+

grpc.gateway.testing.ServerStreamingEchoRequest:
+-----------------+-------------------+--------------+
|   Field Name    |    Field Type     | Field Number |
+=================+===================+==============+
| Message         | Proto3StringField | 1            |
+-----------------+-------------------+--------------+
| MessageCount    | Proto3IntField    | 2            |
+-----------------+-------------------+--------------+
| MessageInterval | Proto3IntField    | 3            |
+-----------------+-------------------+--------------+

grpc.gateway.testing.ServerStreamingEchoResponse:
+------------+-------------------+--------------+
| Field Name |    Field Type     | Field Number |
+============+===================+==============+
| Message    | Proto3StringField | 1            |
+------------+-------------------+--------------+

grpc.gateway.testing.ClientStreamingEchoRequest:
+------------+-------------------+--------------+
| Field Name |    Field Type     | Field Number |
+============+===================+==============+
| Message    | Proto3StringField | 1            |
+------------+-------------------+--------------+

grpc.gateway.testing.ClientStreamingEchoResponse:
+--------------+----------------+--------------+
|  Field Name  |   Field Type   | Field Number |
+==============+================+==============+
| MessageCount | Proto3IntField | 1            |
+--------------+----------------+--------------+

Bridging and JSON transcoding gotchas

Many deployments put an Envoy (or similar) proxy in front of the gRPC server:

  • grpc_web filter translates HTTP/1.1 POSTs into HTTP/2 gRPC.
  • gRPC‑JSON Transcoder exposes gRPC methods as HTTP JSON endpoints when .proto options (google.api.http) are present.

From a pentesting perspective:

  • Try direct HTTP JSON calls to /./ with application/json when a transcoder is enabled (auth/route mismatches are common):
bash
curl -i https://host.tld/pkg.svc.v1.Service/Method \
  -H 'Content-Type: application/json' \
  -d '{"field":"value"}'
  • Review whether unknown methods/parameters are rejected or passed through. Some configs forward unmatched paths upstream, occasionally bypassing auth or request validation.
  • Observe x-envoy-original-path and related headers added by proxies. Upstreams that trust these may be abusable if the proxy fails to sanitize them.

References

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks