Pentesting gRPC-Web
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๋น ๋ฅธ ํ๋กํ ์ฝ ์์ฝ ๋ฐ ๊ณต๊ฒฉ ํ๋ฉด
- Transport: gRPCโWeb๋ ํ๋ก์(Envoy/APISIX/grpcwebproxy/etc.)๋ฅผ ํตํด HTTP/1.1 ๋๋ HTTP/2 ์์์ ๋ธ๋ผ์ฐ์ ํธํ gRPC ๋ณํ์ ์ฌ์ฉํฉ๋๋ค. ์ค์ง unary ๋ฐ serverโstreaming ํธ์ถ๋ง ์ง์๋ฉ๋๋ค.
- Content-Types you will see:
- application/grpc-web (๋ฐ์ด๋๋ฆฌ ํ๋ ์ด๋ฐ)
- application/grpc-web-text (HTTP/1.1 ์คํธ๋ฆฌ๋ฐ์ ์ํ base64 ์ธ์ฝ๋ฉ ํ๋ ์ด๋ฐ)
- Framing: ๋ชจ๋ ๋ฉ์์ง๋ 5โbyte gRPC ํค๋(1โbyte ํ๋๊ทธ + 4โbyte ๊ธธ์ด)๊ฐ ์ ๋๋ฉ๋๋ค. In gRPCโWeb, trailers (grpc-status, grpc-message, โฆ)๋ ๋ณธ๋ฌธ ๋ด๋ถ์ ํน์ ํ๋ ์์ผ๋ก ์ ์ก๋ฉ๋๋ค: MSB๊ฐ ์ค์ ๋ ์ฒซ ๋ฐ์ดํธ(0x80) ๋ค์์ ๊ธธ์ด์ HTTP/1.1โ์คํ์ผ ํค๋ ๋ธ๋ก์ด ์ต๋๋ค.
- Common request headers: x-grpc-web: 1, x-user-agent: grpc-web-javascript/โฆ, grpc-timeout, grpc-encoding. ์๋ต์ grpc-status/grpc-message๋ฅผ trailers/body ํ๋ ์์ ํตํด ๋ ธ์ถํ๋ฉฐ, ๋ธ๋ผ์ฐ์ ์ ๊ฒฝ์ฐ ์ข ์ข Access-Control-Expose-Headers๋ฅผ ํตํด์๋ ๋ ธ์ถํฉ๋๋ค.
- ๋ณด์ ๊ด๋ จ ๋ฏธ๋ค์จ์ด(์์ฃผ ์กด์ฌ):
- Envoy grpc_web filter ๋ฐ gRPCโJSON transcoder (HTTP<->gRPC ๋ธ๋ฆฌ์ง)
- Nginx/APISIX gRPCโWeb ํ๋ฌ๊ทธ์ธ
- ํ๋ก์์ CORS ์ ์ฑ
์ด๊ฒ์ด ๊ณต๊ฒฉ์์๊ฒ ์๋ฏธํ๋ ๋ฐ:
- ์์ฒญ์ ์์ผ๋ก ์์ฑํ ์ ์์ต๋๋ค(๋ฐ์ด๋๋ฆฌ ๋๋ base64 ํ ์คํธ), ๋๋ ๋๊ตฌ๊ฐ ์์ฑ/์ธ์ฝ๋ฉํ๋๋ก ํ ์ ์์ต๋๋ค.
- ํ๋ก์์ CORS ์ค์ ์ค๋ฅ๋ ๊ต์ฐจ ์ฌ์ดํธ ์ธ์ฆ๋ gRPCโWeb ํธ์ถ์ ํ์ฉํ ์ ์์ต๋๋ค(๊ธฐ์กด CORS ๋ฌธ์ ์ ์ ์ฌ).
- JSON transcoding ๋ธ๋ฆฌ์ง๋ ๋ผ์ฐํธ/์ธ์ฆ์ด ์๋ชป ๊ตฌ์ฑ๋ ๊ฒฝ์ฐ gRPC ๋ฉ์๋๋ฅผ ์๋์น ์๊ฒ ์ธ์ฆ๋์ง ์์ HTTP ์๋ํฌ์ธํธ๋ก ๋ ธ์ถํ ์ ์์ต๋๋ค.
CLI์์ gRPCโWeb ํ ์คํธํ๊ธฐ
๊ฐ์ฅ ์ฌ์: buf curl (gRPCโWeb์ ๋ค์ดํฐ๋ธ๋ก ์ง์)
- reflection์ ํตํด ๋ฉ์๋ ๋ชฉ๋ก ํ์ธ (ํ์ฑํ๋ ๊ฒฝ์ฐ):
# list methods (uses reflection)
buf curl --protocol grpcweb https://host.tld --list-methods
- JSON ์ ๋ ฅ์ผ๋ก ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ , gRPCโWeb ํ๋ ์ด๋ฐ๊ณผ headers๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ:
buf curl --protocol grpcweb \
-H 'Origin: https://example.com' \
-d '{"field":"value"}' \
https://host.tld/pkg.svc.v1.Service/Method
- reflection์ด ๋นํ์ฑํ๋ ๊ฒฝ์ฐ, โschema ์ต์ ์ผ๋ก schema/descriptor set์ ์ ๊ณตํ๊ฑฐ๋ ๋ก์ปฌ .proto ํ์ผ์ ๊ฐ๋ฆฌํค์ธ์. ์์ธํ ๋ด์ฉ์ buf help curl์ ์ฐธ์กฐํ์ธ์.
Raw with curl (์๋ ํค๋ + ํ๋ ์๋ ๋ฐ๋)
๋ฐ์ด๋๋ฆฌ ๋ชจ๋(application/grpc-web)์์๋ ํ๋ ์๋ ํ์ด๋ก๋(5โbyte ์ ๋์ฌ + protobuf message)๋ฅผ ์ ์กํ์ธ์. ํ ์คํธ ๋ชจ๋์์๋ ํ๋ ์๋ ํ์ด๋ก๋๋ฅผ base64โencodeํ์ธ์.
# 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
ํ: HTTP/1.1 ์ค๊ฐ์๊ฐ ๋ฐ์ด๋๋ฆฌ ์คํธ๋ฆฌ๋ฐ์ ๋ฐฉํดํ ๋ application/grpc-web-text๋ก base64/text ๋ชจ๋๋ฅผ ๊ฐ์ ํ์ธ์.
CORS ๋์ ํ์ธ (ํ๋ฆฌํ๋ผ์ดํธ + ์๋ต)
- ํ๋ฆฌํ๋ผ์ดํธ:
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'
- ์ทจ์ฝํ ์ค์ ์ ์ข ์ข ์์ Origin์ ๋ฐ์ฌํ๊ณ Access-Control-Allow-Credentials: true๋ฅผ ์ ์กํ์ฌ ๊ต์ฐจ ์ฌ์ดํธ ์ธ์ฆ๋ ํธ์ถ์ ํ์ฉํฉ๋๋ค. ๋ํ Access-Control-Expose-Headers์ grpc-status, grpc-message๊ฐ ํฌํจ๋์ด ์๋์ง ํ์ธํ์ธ์(๋ง์ ๋ฐฐํฌ์์ ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ํด ์ด๋ฅผ ๋ ธ์ถํฉ๋๋ค).
CORS๋ฅผ ์ ์ฉํ๊ธฐ ์ํ ์ผ๋ฐ์ ์ธ ๊ธฐ๋ฒ์ CORS - Misconfigurations & Bypass๋ฅผ ํ์ธํ์ธ์.
gRPCโWeb ํ์ด๋ก๋ ์กฐ์
gRPCโWeb๋ ๋ธ๋ผ์ฐ์ ํธํ์ ์ํด Content-Type: application/grpc-web-text๋ก base64๋ก ๋ํ๋ gRPC ํ๋ ์ ์คํธ๋ฆผ์ ์ฌ์ฉํฉ๋๋ค. ํ๋ ์์ ๋์ฝ๋/์์ /์ธ์ฝ๋ํ์ฌ ํ๋๋ฅผ ๋ณ์กฐํ๊ฑฐ๋ ํ๋๊ทธ๋ฅผ ๋ค์ง๊ฑฐ๋ ํ์ด๋ก๋๋ฅผ ์ฃผ์ ํ ์ ์์ต๋๋ค.
์๋๋ฅผ ๋์ด๋ ค๋ฉด gprc-coder ๋๊ตฌ(๋ฐ Burp ํ์ฅ)๋ฅผ ์ฌ์ฉํ์ธ์.
gGRPC Coder Tool์ ์ฌ์ฉํ ์๋ ๋ฐฉ๋ฒ
- ํ์ด๋ก๋ ๋์ฝ๋:
echo "AAAAABYSC0FtaW4gTmFzaXJpGDY6BVhlbm9u" | python3 grpc-coder.py --decode --type grpc-web-text | protoscope > out.txt
- ๋์ฝ๋ฉ๋ ํ์ด๋ก๋์ ๋ด์ฉ์ ํธ์ง
nano out.txt
2: {"Amin Nasiri Xenon GRPC"}
3: 54
7: {"<script>alert(origin)</script>"}
- ์๋ก์ด payload๋ฅผ ์ธ์ฝ๋ฉ
protoscope -s out.txt | python3 grpc-coder.py --encode --type grpc-web-text
- Burp interceptor์์ ์ถ๋ ฅ์ ์ฌ์ฉ:
AAAAADoSFkFtaW4gTmFzaXJpIFhlbm9uIEdSUEMYNjoePHNjcmlwdD5hbGVydChvcmlnaW4pPC9zY3JpcHQ+
gRPCโWeb Coder Burp Suite Extension ์ฌ์ฉ ๋งค๋ด์ผ
๋ ์ฌ์ด ๋ฐฉ๋ฒ์ผ๋ก gRPCโWeb Pentest Suite์ ํฌํจ๋ gRPCโWeb Coder Burp Suite Extension์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ค์น ๋ฐ ์ฌ์ฉ๋ฒ์ ํด๋น ์ ์ฅ์์์ ํ์ธํ์ธ์.
gRPCโWeb JavaScript ํ์ผ ๋ถ์
gRPCโWeb๋ฅผ ์ฌ์ฉํ๋ ์น ์ฑ์ ์ต์ ํ๋ ์ด์์ ์์ฑ๋ JS/TS ๋ฒ๋ค์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ์ญ๋ถ์ํ์ฌ ์๋น์ค, ๋ฉ์๋, ๋ฉ์์ง ํ์์ ์ถ์ถํ์ธ์.
- ๋ฒ๋ค ํ์ฑ์๋ gRPC-Scan์ ์ฌ์ฉํด๋ณด์ธ์.
- /
. / ์ ๊ฐ์ ๋ฉ์๋ ๊ฒฝ๋ก, ๋ฉ์์ง ํ๋ ๋ฒํธ/ํ์ , ์ธ์ฆ ํค๋๋ฅผ ์ถ๊ฐํ๋ ์ปค์คํ ์ธํฐ์ ํฐ๋ฅผ ์ฐพ์๋ณด์ธ์.
- JavaScript gRPCโWeb ํ์ผ์ ๋ค์ด๋ก๋ํ์ธ์
- grpc-scan.py๋ก ์ค์บํ์ธ์:
python3 grpc-scan.py --file main.js
- ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ถ์ํ๊ณ ์ ์๋ํฌ์ธํธ์ ์ ์๋น์ค๋ฅผ ํ ์คํธํฉ๋๋ค:
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 |
+--------------+----------------+--------------+
๋ธ๋ฆฌ์ง ๋ฐ JSON ํธ๋์ค์ฝ๋ฉ ์ฃผ์์ฌํญ
๋ง์ ๋ฐฐํฌ์์๋ gRPC ์๋ฒ ์์ Envoy(๋๋ ์ ์ฌํ) ํ๋ก์๋ฅผ ๋ก๋๋ค:
- grpc_web filter๋ HTTP/1.1 POST๋ฅผ HTTP/2 gRPC๋ก ๋ณํํฉ๋๋ค.
- gRPCโJSON Transcoder๋ .proto ์ต์ (google.api.http)์ด ์์ ๋ gRPC ๋ฉ์๋๋ฅผ HTTP JSON ์๋ํฌ์ธํธ๋ก ๋ ธ์ถํฉ๋๋ค.
From a pentesting perspective:
- transcoder๊ฐ ํ์ฑํ๋์ด ์์ ๋ application/json์ผ๋ก /
. / ์ ์ง์ HTTP JSON ํธ์ถ์ ์๋ํด๋ณด์ธ์ (auth/route mismatches are common):
curl -i https://host.tld/pkg.svc.v1.Service/Method \
-H 'Content-Type: application/json' \
-d '{"field":"value"}'
- ์ ์ ์๋ methods/parameters๊ฐ ๊ฑฐ๋ถ๋๋์ง ๋๋ ํต๊ณผ๋๋์ง ๊ฒํ ํ์ธ์. ์ผ๋ถ ๊ตฌ์ฑ์ ์ผ์นํ์ง ์๋ ๊ฒฝ๋ก๋ฅผ upstream์ผ๋ก ์ ๋ฌํ์ฌ ๋๋๋ก auth ๋๋ request validation์ ์ฐํํ ์ ์์ต๋๋ค.
- ํ๋ก์๊ฐ ์ถ๊ฐํ๋ x-envoy-original-path ๋ฐ ๊ด๋ จ headers๋ฅผ ๊ด์ฐฐํ์ธ์. ์ด๋ฌํ ํค๋๋ฅผ ์ ๋ขฐํ๋ upstreams๋ ํ๋ก์๊ฐ ์ด๋ฅผ sanitizeํ์ง ๋ชปํ๋ฉด ์ ์ฉ๋ ์ ์์ต๋๋ค.
์ฐธ๊ณ ์๋ฃ
- Hacking into gRPCโWeb Article by Amin Nasiri
- gRPCโWeb Pentest Suite
- gRPCโWeb protocol notes (PROTOCOLโWEB.md)
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


