webfreehard

Turncoat's Treasure

umasscybersec

Task: a forum app exposed stored XSS, a wildcard Host-based proxy, and captain-only endpoints hidden behind nginx path filters. Solution: bypass the lowercase proxy block with uppercase Express routes, make the bot visit the XSS page, and leak the localhost-only treasure through CSS selector injection.

$ ls tags/ techniques/
stored_xss_to_bothost_header_internal_routingcase_sensitive_route_bypasscss_exfiltration

Turncoat's Treasure — UMass Cybersecurity CTF

Description

Organizer description was not preserved in the local task files.

English summary: the challenge provided a forum-style web app plus hidden captain functionality. The goal was to pivot from a stored XSS in user profiles to an internal service, then exfiltrate a localhost-only flag despite same-origin and CORS restrictions.

Analysis

After downloading the assets and reviewing the source, the first useful bug appeared in the profile page template:

{{ p.content | safe }}

That made /user/:username a stored XSS sink. Any JavaScript placed in a forum post or profile content would execute when that profile page was rendered.

The next interesting components were the captain endpoints:

  • /call-captain
  • /treasure

At first glance they looked unreachable because nginx explicitly blocked those lowercase paths. However, the deployment also used a wildcard subdomain proxy that routed requests by Host. By sending a host like 10.128.6.2.<instancehost>, traffic could be forwarded to the internal captain service.

The key bypass was a mismatch between the reverse proxy and the backend:

  1. nginx location matching was case-sensitive for the blocking rule.
  2. Express routing was case-insensitive by default.

That meant /CALL-CAPTAIN was not caught by nginx's lowercase deny rule, but Express still treated it as /call-captain and served the captain logic. The same applied to /TREASURE, but the captain service itself still limited /treasure to localhost, so an extra pivot was required.

This naturally suggested using the bot. By requesting:

GET /CALL-CAPTAIN?endpoint=/user/<attacker_username> Host: 10.128.6.2.<instancehost>

the captain bot was convinced to visit our stored-XSS profile page from inside the internal network context.

...

🔒

Permission denied (requires auth)

Sign in to read this free writeup

This writeup is free — just sign in with GitHub to read it.

$ssh [email protected]