gRPC is a high-performance Remote Procedure Call (RPC) framework initially created by Google for efficient data serialization and deserialization, commonly used for service-to-service communication in back-end systems. Since it relies on specific HTTP/2 features that are not supported in browser-based JavaScript environments, gRPC is not natively compatible with browsers. To address this, a slightly modified version called gRPC-Web was created. This allows front-end applications to communicate with gRPC-compatible backend systems via gRPC-Web.

The gRPC framework, and by extension gRPC-Web, is based on Protocol Buffers (Protobuf), a binary data serialization format that is more efficient in terms of both speed and size than JSON or XML. However, this binary data format is not human-readable. Depending on the encoding mode used, either a raw binary blob or a Base64-encoded version thereof is sent in the request and response body. This poses a challenge for penetration testers when intercepting browser to server communication with tools such as Burp Suite.

In our experience, gRPC-Web is relatively uncommon, but it is important to have adequate tooling available when it does appear. This project was initially started after we unexpectedly encountered gRPC-Web during a penetration test a few years ago. Today, we are releasing our Burp Suite extension bRPC-Web in the hope that it will prove useful to others during their assessments.

The remainder of this blog post gives a brief overview of the tool using a simple gRPC-Web demo service. We assume a basic understanding of Protobuf and gRPC. For those new to this topic, reading the gRPC introduction and skimming over the gRPC core concepts should be sufficient to follow the article. Familiarity with the Protocol Buffer wire format is helpful to gain a deeper understanding of the internal workings of the extension.

gRPC-WebEcho Service

To demonstrate the tool, we built a simple gRPC-Web demo called Echo. The web client sends a gRPC-web request, and the server responds by echoing back the request body. The source code is available at https://github.com/muukong/grpc-web-echo-test.

The Echo service is defined by the following Protobuf definition file:

syntax = "proto3";

package echo;

service EchoService {
  // unary call
  rpc Echo(EchoRequest) returns (EchoReply);

  // server streaming call
  rpc EchoStream(EchoRequestStream) returns (stream EchoReply);
}

message EchoRequest {
  string a = 1;
  SubMessage1 b = 7;
  SubMessage2 c = 13;
}

message SubMessage1 {
  string a = 42;
}

message SubMessage2 {
  int64 a = 137;
}

message EchoRequestStream {
  EchoRequest payload = 1;
  int64 count = 2;
}

message EchoReply {
  EchoRequest request = 8;
}

This Protobuf definition declares a gRPC service named EchoService with two methods:

  • Echo: A unary RPC that takes an EchoRequest and returns an EchoReply.
  • EchoStream: A server-streaming RPC that takes an EchoRequestStream and returns a stream of EchoReply messages.

The EchoRequest message contains three fields:

  • a: a simple string (field number 1),
  • b: a nested message of type SubMessage1 (field number 7),
  • c: a nested message of type SubMessage2 (field number 13).

SubMessage1 holds a single string field labeled a with field number 42, and SubMessage2 contains a 64-bit integer labeled a with field number 137.

EchoRequestStream wraps an EchoRequest payload and a count field to specify how many times the message should be echoed back in the streaming response. The EchoReply message is used for echoing back the incoming EchoRequest by embedding it as a nested field labeled request (field number 8).

The following JavaScript code snippet instantiates an EchoRequest object:

var client = new EchoServiceClient('http://' + window.location.hostname + ':8888',
                                                                     null, null);

var subMessage1 = new SubMessage1();
subMessage1.setA('Hello from sub message 1');

var subMessage2 = new SubMessage2();
subMessage2.setA(1337);

var request1 = new EchoRequest();
request1.setA('hello');
request1.setB(subMessage1);
request1.setC(subMessage2);

The serialized Protobuf object looks as follows (the binary blob was decoded with the xxd command line tool):

00000000: 0000 0000 2c42 2a0a 0568 656c 6c6f 3a1b  ....,B*..hello:.
00000010: d202 1848 656c 6c6f 2066 726f 6d20 7375  ...Hello from su
00000020: 6220 6d65 7373 6167 6520 316a 04c8 08b9  b message 1j....
00000030: 0a80 0000 0020 6772 7063 2d73 7461 7475  ..... grpc-statu
00000040: 733a 300d 0a67 7270 632d 6d65 7373 6167  s:0..grpc-messag
00000050: 653a 4f4b 0d0a                           e:OK..

Reading and modifying this binary blob by hand is obviously a tedious and error-prone endeavor and is certainly not practical for a penetration test. The purpose of the extension is to decode such messages into a human-readable form and allow interactive editing.

Given a schema definition such as the one above, building a basic encoder and decoder is relatively straightforward. However, in a typical (black box) penetration test such definition files are usually not available, and it’s often not clear how this information can be recovered or reverse engineered efficiently. The bRCP-Web extension attempts to decode the message using a heuristic backtracking algorithm that does not rely on Protobuf definition files. While the approach is inherently imperfect since it’s always possible to craft an example that leads to decoding errors, it appears to work well in practice.

The extension integrates with Burp Suite’s Proxy and Repeater tools, as shown below.

bRPC-Web Extension

HTTP Proxy

An HTTP example with content type application/grpc-web+proto where the request and response body is a binary blob:

The same request can also be sent with content type application/grpc-web-text, in which case the binary blob is Base64-encoded:

In either case, the request and response body are not human-readable. The extension adds a gRPC-Web tab that decodes the message and displays it in the human-readable Protoscope format:

The gRPC-Web protocol supports streaming, where the server responds with multiple messages. The extension can identify and decode multiple embedded messages. In the example below, the server echoes the message five times (as defined by the count value on line 12 in the request):

Repeater

The gRPC-Web tab is also available in Repeater. Any part of the decoded request or response can be modified. In addition to editing fields, it is also possible to add or remove fields:

(It should be noted that the service does not echo back manually added fields. To verify that the fields were indeed added to the request, Burp Suite’s Logger tool can be used.)

Support for gRPC

At the time of writing, the extension handles only gRPC-Web traffic, as Burp Suite does not yet support gRPC. However, since the encoding and decoding logic already works with plain gRPC messages, extending the functionality should be relatively straightforward once PortSwigger adds gRPC support to Burp Suite.

References

Resources

Tools