| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
|
| 11 |
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 12 |
source "$SCRIPT_DIR/../config.sh" |
| 13 |
OUT_FILE="$OUT/32_grpc_protobuf.txt" |
| 14 |
echo "=== 32. gRPC / Protobuf ===" | tee "$OUT_FILE" |
| 15 |
echo "Target: $TARGET API: $API_TARGET" | tee -a "$OUT_FILE" |
| 16 |
echo "" | tee -a "$OUT_FILE" |
| 17 |
|
| 18 |
HOST=$(echo "$TARGET" | sed 's|https\?://||' | cut -d/ -f1) |
| 19 |
IP=$(dig +short "$HOST" | grep -oP '^\d+\.\d+\.\d+\.\d+$' | head -1) |
| 20 |
[ -z "$IP" ] && IP="$HOST" |
| 21 |
|
| 22 |
| 23 |
echo "--- gRPC Port Scan ---" | tee -a "$OUT_FILE" |
| 24 |
GRPC_PORTS=(50051 50052 50053 9090 9091 9092 8080 8443 3000 3001 4000 4001 7070 7071) |
| 25 |
FOUND_PORTS=() |
| 26 |
for PORT in "${GRPC_PORTS[@]}"; do |
| 27 |
if timeout 2 bash -c "echo >/dev/tcp/$IP/$PORT" 2>/dev/null; then |
| 28 |
printf ' [OPEN] port %s on %s\n' "$PORT" "$IP" | tee -a "$OUT_FILE" |
| 29 |
FOUND_PORTS+=("$PORT") |
| 30 |
else |
| 31 |
printf ' [CLOSED] port %s\n' "$PORT" | tee -a "$OUT_FILE" |
| 32 |
fi |
| 33 |
done |
| 34 |
|
| 35 |
if [ ${#FOUND_PORTS[@]} -eq 0 ]; then |
| 36 |
echo "" | tee -a "$OUT_FILE" |
| 37 |
echo " [SCOPE] No gRPC ports found on public surface." | tee -a "$OUT_FILE" |
| 38 |
echo " If internal services use gRPC, test from within the network or VPN." | tee -a "$OUT_FILE" |
| 39 |
echo " See SCOPE.md for gRPC coverage gap documentation." | tee -a "$OUT_FILE" |
| 40 |
fi |
| 41 |
echo "" | tee -a "$OUT_FILE" |
| 42 |
|
| 43 |
| 44 |
echo "--- gRPC Content-Type Probe (HTTP/2) ---" | tee -a "$OUT_FILE" |
| 45 |
| 46 |
| 47 |
GRPC_CT_PROBES=( |
| 48 |
"application/grpc" |
| 49 |
"application/grpc+proto" |
| 50 |
"application/grpc-web" |
| 51 |
"application/grpc-web+proto" |
| 52 |
"application/grpc-web-text" |
| 53 |
) |
| 54 |
for CT in "${GRPC_CT_PROBES[@]}"; do |
| 55 |
for ENDPOINT in "/" "/encrypt" "/decrypt" "/status" "/grpc" "/api"; do |
| 56 |
CODE=$(curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 8 \ |
| 57 |
-A "$BROWSER_UA" \ |
| 58 |
-X POST \ |
| 59 |
-H "Content-Type: $CT" \ |
| 60 |
-H "TE: trailers" \ |
| 61 |
--data-binary $'\x00\x00\x00\x00\x05\x08\x01\x10\x01\x18\x01' \ |
| 62 |
"$API_TARGET$ENDPOINT") |
| 63 |
| 64 |
| 65 |
| 66 |
if [ "$CODE" != "415" ] && [ "$CODE" != "404" ] && [ "$CODE" != "401" ] && [ "$CODE" != "403" ]; then |
| 67 |
printf ' [%s] %s โ %s (unexpected โ check if gRPC endpoint)\n' "$CODE" "$CT" "$ENDPOINT" | tee -a "$OUT_FILE" |
| 68 |
else |
| 69 |
printf ' [%s] %s โ %s\n' "$CODE" "$CT" "$ENDPOINT" | tee -a "$OUT_FILE" |
| 70 |
fi |
| 71 |
done |
| 72 |
done |
| 73 |
echo "" | tee -a "$OUT_FILE" |
| 74 |
|
| 75 |
| 76 |
echo "--- gRPC Server Reflection (service enumeration) ---" | tee -a "$OUT_FILE" |
| 77 |
| 78 |
| 79 |
| 80 |
REFLECT_PAYLOAD=$(python3 -c " |
| 81 |
import struct, sys |
| 82 |
| 83 |
proto_body = b'\x0a\x00' # field 1 (string): empty |
| 84 |
frame = struct.pack('>BI', 0, len(proto_body)) + proto_body |
| 85 |
sys.stdout.buffer.write(frame) |
| 86 |
" 2>/dev/null | base64 -w0) |
| 87 |
|
| 88 |
for PORT in 443 50051 9090 8080; do |
| 89 |
TARGET_REFLECT="https://$HOST:$PORT/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo" |
| 90 |
CODE=$(echo "$REFLECT_PAYLOAD" | base64 -d | \ |
| 91 |
curl -sk --http2 -o /tmp/grpc_reflect.bin -w '%{http_code}' \ |
| 92 |
--max-time 8 -A "$BROWSER_UA" \ |
| 93 |
-X POST \ |
| 94 |
-H "Content-Type: application/grpc" \ |
| 95 |
-H "TE: trailers" \ |
| 96 |
--data-binary @- \ |
| 97 |
"$TARGET_REFLECT" 2>/dev/null) |
| 98 |
RESP_HEX=$(xxd /tmp/grpc_reflect.bin 2>/dev/null | head -3) |
| 99 |
if [ "$CODE" = "200" ]; then |
| 100 |
echo " [VULN] gRPC reflection responded on port $PORT โ service enumeration possible!" | tee -a "$OUT_FILE" |
| 101 |
echo "$RESP_HEX" | sed 's/^/ /' | tee -a "$OUT_FILE" |
| 102 |
else |
| 103 |
printf ' [%s] gRPC reflection probe on port %s\n' "$CODE" "$PORT" | tee -a "$OUT_FILE" |
| 104 |
fi |
| 105 |
done |
| 106 |
echo "" | tee -a "$OUT_FILE" |
| 107 |
|
| 108 |
| 109 |
echo "--- Protobuf Malformed Frame Fuzzing ---" | tee -a "$OUT_FILE" |
| 110 |
FUZZ_CASES=( |
| 111 |
"Empty frame:\x00\x00\x00\x00\x00" |
| 112 |
"Oversized length:\x00\xff\xff\xff\xff" |
| 113 |
"Truncated payload:\x00\x00\x00\x00\x10\x08\x01" |
| 114 |
"Compressed flag set:\x01\x00\x00\x00\x05\x08\x01\x10\x01\x18\x01" |
| 115 |
"Max field number:\xff\xff\xff\xff\x7f" |
| 116 |
"Nested overflow:\x0a\xff\xff\xff\x7f" |
| 117 |
) |
| 118 |
for CASE in "${FUZZ_CASES[@]}"; do |
| 119 |
LABEL="${CASE%%:*}" |
| 120 |
PAYLOAD="${CASE#*:}" |
| 121 |
CODE=$(printf "$PAYLOAD" | curl -sk --http2 -o /dev/null -w '%{http_code}' \ |
| 122 |
--max-time 8 -A "$BROWSER_UA" \ |
| 123 |
-X POST \ |
| 124 |
-H "Content-Type: application/grpc" \ |
| 125 |
-H "TE: trailers" \ |
| 126 |
--data-binary @- \ |
| 127 |
"$API_TARGET/encrypt" 2>/dev/null) |
| 128 |
printf ' [%s] Protobuf fuzz: %s\n' "$CODE" "$LABEL" | tee -a "$OUT_FILE" |
| 129 |
done |
| 130 |
echo "" | tee -a "$OUT_FILE" |
| 131 |
|
| 132 |
| 133 |
echo "--- gRPC-Web Gateway ---" | tee -a "$OUT_FILE" |
| 134 |
| 135 |
GRPC_WEB_PATHS=(/grpc-web /grpcweb /grpc.gateway /api/grpc /v1/grpc) |
| 136 |
for GWPATH in "${GRPC_WEB_PATHS[@]}"; do |
| 137 |
CODE=$(curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 8 \ |
| 138 |
-A "$BROWSER_UA" \ |
| 139 |
-X POST \ |
| 140 |
-H "Content-Type: application/grpc-web+proto" \ |
| 141 |
-H "X-Grpc-Web: 1" \ |
| 142 |
--data-binary $'\x00\x00\x00\x00\x00' \ |
| 143 |
"$TARGET$GWPATH" 2>/dev/null) |
| 144 |
[ "$CODE" != "404" ] && [ "$CODE" != "301" ] && \ |
| 145 |
printf ' [%s] gRPC-Web gateway: %s\n' "$CODE" "$GWPATH" | tee -a "$OUT_FILE" || \ |
| 146 |
printf ' [%s] %s\n' "$CODE" "$GWPATH" | tee -a "$OUT_FILE" |
| 147 |
done |
| 148 |
echo "" | tee -a "$OUT_FILE" |
| 149 |
|
| 150 |
| 151 |
echo "--- gRPC Metadata Auth Bypass ---" | tee -a "$OUT_FILE" |
| 152 |
| 153 |
for ENDPOINT in "/encrypt" "/decrypt" "/status"; do |
| 154 |
| 155 |
CODE=$(printf '\x00\x00\x00\x00\x05\x08\x01\x10\x01\x18\x01' | \ |
| 156 |
curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 8 \ |
| 157 |
-X POST \ |
| 158 |
-H "Content-Type: application/grpc" \ |
| 159 |
-H "TE: trailers" \ |
| 160 |
--data-binary @- \ |
| 161 |
"$API_TARGET$ENDPOINT" 2>/dev/null) |
| 162 |
printf ' [%s] gRPC no-auth โ %s\n' "$CODE" "$ENDPOINT" | tee -a "$OUT_FILE" |
| 163 |
|
| 164 |
| 165 |
CODE=$(printf '\x00\x00\x00\x00\x05\x08\x01\x10\x01\x18\x01' | \ |
| 166 |
curl -sk --http2 -o /dev/null -w '%{http_code}' --max-time 8 \ |
| 167 |
-X POST \ |
| 168 |
-H "Content-Type: application/grpc" \ |
| 169 |
-H "TE: trailers" \ |
| 170 |
-H "authorization: Bearer FAKE_GRPC_TOKEN" \ |
| 171 |
--data-binary @- \ |
| 172 |
"$API_TARGET$ENDPOINT" 2>/dev/null) |
| 173 |
printf ' [%s] gRPC fake-auth โ %s\n' "$CODE" "$ENDPOINT" | tee -a "$OUT_FILE" |
| 174 |
done |
| 175 |
echo "" | tee -a "$OUT_FILE" |
| 176 |
|
| 177 |
echo "=== 32. gRPC / Protobuf COMPLETE ===" | tee -a "$OUT_FILE" |
| 178 |
|