Lab 234 — RouteForge — HTTP Request Smuggling via Obs-Fold Header Parsing
Lab 234 — RouteForge — HTTP Request Smuggling via Obs-Fold Header Parsing
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: path_normalization_bypass, raw_http_probing
Summary
Walkthrough of the CUSTOM Web Lab 234 — RouteForge — HTTP Request Smuggling via Obs-Fold Header Parsing. Task: Reach protected RouteForge administration routes through an nginx, Node.js, and Python proxy chain suggesting an obs-fold parser mismatch. Solution: Preserve /./ in the raw URL to bypass the pro
Description
Lab 234 — RouteForge — HTTP Request Smuggling via Obs-Fold Header Parsing
The application provided a normal user account (user@test.com / password123) and asked us to recover a secret from a protected administration route. Although the title explicitly suggested obsolete folded-header (obs-fold) request smuggling, the deployed public nginx rejected such requests before they reached the vulnerable-looking inner proxy chain.
The verified solution was instead an alternate, likely unintended path-normalization bypass. Sending /./admin/secret-keys without client-side path normalization evaded the RouteForge ACL, while downstream normalization routed the request to /admin/secret-keys.
Architecture and Reconnaissance
The observable request path was:
text read-only • copy allowedclient -> nginx/1.25.5 (public TLS endpoint) -> RouteForge/2.4.1 Node.js proxy on port 8080 -> Python backend on port 3001
The application exposed enough route and gateway information to identify two interesting paths:
text read-only • copy allowed/admin/secret-keys /admin/config
After authentication, direct requests to both routes returned a RouteForge JSON 403 response. The X-Gateway: RouteForge/2.4.1 header showed that the denial came from the proxy ACL rather than from the Python route itself.
The initial HTML also contained this value:
text read-only • copy allowedFLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}
It is a deliberate decoy, not the challenge flag. The real HackAdvisor flag follows the expected form of 32 lowercase hexadecimal characters inside FLAG{...}.
Analysis
Why the suggested obs-fold attack was not reachable
An obs-fold header is a deprecated HTTP/1.x continuation line beginning with a space or horizontal tab. A proxy/backend disagreement over a folded Transfer-Encoding or Content-Length header could theoretically produce CL.TE, TE.CL, or another request-boundary desynchronization.
Raw TLS requests tested all of the ordinary variants relevant to that theory:
- a folded arbitrary header using space and HTAB continuations;
- folded
Transfer-Encoding: chunkedforms; - folded
Content-Lengthforms; - CRLF and bare-LF line endings;
- CL.TE and TE.CL request bodies followed by a second
/admin/secret-keysrequest; - a folded chunk trailer.
Every ordinary folded request-header form received an nginx-generated 400 Bad Request. It therefore never reached RouteForge or the Python backend. A folded chunk trailer was accepted, but nginx 1.25 did not forward that request trailer upstream, so it provided no smuggling primitive.
Two possible ways around the outer parser were also checked:
- Direct HTTP and HTTPS access to port 8080 was unavailable.
- WebSocket upgrade probes against common tunnel paths and the known API routes never returned
101 Switching Protocols.
Thus, the intended inner obs-fold parser discrepancy may have existed on the Node/Python hop, but it was not externally reachable through the supplied nginx deployment.
Path-normalization mismatch
The title's “RouteForge” wording also suggested route confusion. Testing equivalent spellings of the protected URL revealed a mismatch:
text read-only • copy allowedRouteForge ACL sees: /./admin/secret-keys ACL comparison result: does not match /admin/secret-keys Downstream normalized to: /admin/secret-keys Python route result: protected endpoint executes
The request returned HTTP 200 and the secret-key JSON. Its Platform Service Token value was the real flag, confirming that this was not merely an unusual status-code response but a successful protected-route bypass.
Solution
1. Authenticate and save the session cookie
Set the instance URL without a trailing slash, then submit the supplied form credentials. The cookie jar contains the ephemeral session value, so no hardcoded cookie is needed:
bash read-only • copy allowedBASE='https://adbc36b9-91a5-4e1e-91dd-52d52897437d.labs.hackadvisor.io' curl -sk \ -c cookies.txt \ -o /dev/null \ -D - \ -X POST "$BASE/login" \ --data-urlencode 'email=user@test.com' \ --data-urlencode 'password=password123'
The login response sets a connect.sid cookie and redirects the authenticated user.
2. Demonstrate the direct ACL denial
bash read-only • copy allowedcurl -ski -b cookies.txt "$BASE/admin/secret-keys"
The direct spelling returns HTTP 403 from RouteForge.
3. Preserve the dot segment and bypass the ACL
bash read-only • copy allowedcurl -ski \ --path-as-is \ -b cookies.txt \ "$BASE/./admin/secret-keys"
The response is HTTP 200 JSON and includes the Platform Service Token record containing:
text read-only • copy allowedFLAG{REDACTED}
Why --path-as-is is required
By default, curl removes RFC dot segments before transmitting the request. Without --path-as-is, the local client changes /./admin/secret-keys into /admin/secret-keys; RouteForge then sees the protected canonical path and blocks it normally.
--path-as-is instructs curl to send the path exactly as written. This preserves /./ on the wire, which is the essential input for triggering the ACL/downstream normalization disagreement.
Minimal reproducible solver
bash read-only • copy allowed#!/usr/bin/env bash set -euo pipefail BASE='https://adbc36b9-91a5-4e1e-91dd-52d52897437d.labs.hackadvisor.io' JAR='cookies.txt' curl -sk -c "$JAR" -o /dev/null \ -X POST "$BASE/login" \ --data-urlencode 'email=user@test.com' \ --data-urlencode 'password=password123' curl -sk --path-as-is -b "$JAR" "$BASE/./admin/secret-keys"
Failed Intended-Smuggling Attempts
The accompanying raw-socket probe authenticated, established TLS directly, and submitted byte-exact HTTP/1.1 requests. Representative rejected forms were:
http read-only • copy allowedTransfer-Encoding: chunked
http read-only • copy allowedX-Pad: x Transfer-Encoding: chunked Content-Length: ...
http read-only • copy allowedContent-Length: 4 Transfer-Encoding: chunked x
Space continuation, HTAB continuation, CRLF, and bare-LF variations all failed at the public nginx with HTTP 400. WebSocket upgrade and direct-port tests did not expose a route around nginx. These results resolve the deployment hypothesis: the suggested obs-fold discrepancy was not reachable externally, whereas the path-normalization mismatch was directly confirmed by the 403-versus-200 behavior.
Takeaway
Parser-differential challenges require testing every layer that receives the request. Here, nginx neutralized the advertised obs-fold attack at the perimeter, but RouteForge still made an authorization decision on a non-canonical path. Authorization should be evaluated against the same canonical path that downstream routing uses.
Key Takeaways / Lessons
- Mastering path_normalization_bypass, raw_http_probing was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-16 • CUSTOM • Lab 234 — RouteForge — HTTP Request Smuggling via Obs-Fold Header Parsing • Difficulty: Medium