webfreemedium

Dusty Alleys

hackthebox

Task: Discover a hidden nginx vhost and exploit SSRF to exfiltrate the flag. Solution: Send an HTTP/1.0 request without Host header to /think, causing nginx to fall back to server_name as $host variable and leak the secret vhost domain, then use the /guardian SSRF endpoint to fetch /think with the flag injected in the Key header.

$ ls tags/ techniques/
http10_host_leaknginx_vhost_discoveryssrf_header_exfiltrationserver_name_fallback

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Dusty Alleys — HackTheBox

Description

"In the dark, dusty underground labyrinth, the survivors feel lost and their resolve weakens. Just as despair sets in, they notice a faint light: a dilapidated, rusty robot emitting feeble sparks. Hoping for answers, they decide to engage with it."

Target: http://154.57.164.81:32496

Architecture

Two-layer setup:

  1. nginx (port 80) — reverse proxy with two virtual host server blocks:
    • alley.$SECRET_ALLEY (default_server) — serves static files at /, proxies /alley and /think to Node.js
    • guardian.$SECRET_ALLEY — proxies /guardian to Node.js
  2. Node.js Express (port 1337) — backend with three routes:
    • GET /alley — renders index page
    • GET /think — returns all received request headers as JSON (header reflection)
    • GET /guardian — SSRF endpoint: takes quote URL parameter, validates hostname ends with "localhost", fetches URL with FLAG in Key header

Key Source: routes/guardian.js

router.get("/guardian", async (req, res) => { const quote = req.query.quote; if (!quote) return res.render("guardian"); try { const location = new URL(quote); const direction = location.hostname; if (!direction.endsWith("localhost") && direction !== "localhost") return res.send("guardian", { error: "You are forbidden from talking with me." }); } catch (error) { return res.render("guardian", { error: "My brain circuits are mad." }); } try { let result = await node_fetch(quote, { method: "GET", headers: { Key: process.env.FLAG || "HTB{REDACTED}" }, }).then((res) => res.text()); res.set("Content-Type", "text/plain"); res.send(result); } catch (e) { return res.render("guardian", { error: "The words are lost in my circuits" }); } });

Key Source: nginx default.conf

...

$ grep --similar

Similar writeups