The title field is reflected as raw HTML, a weird built-in callback gadget turns malformed markup into JavaScript, and the admin bot hands over the flag through a readable same-origin cookie.
raw title HTML injection -> reshape DOM into gadget-friendly state -> abuse built-in cb script gadget under CSP -> admin bot visits crafted same-origin paste -> JS reads readable flag cookie -> redirect to attacker-controlled collector
If you don't understand these, the payload looks cursed and random.
Concept 1: Reflected HTML injection is not automatically XSS. If CSP is strict, normal <script> or onerror= tricks die immediately.
Concept 2: Sometimes the page already contains its own JavaScript execution primitive. If you find one, your job is not to inject JS directly. Your job is to feed that primitive.
Concept 3: Admin bots are dangerous when they browse attacker-controlled same-origin pages while holding secrets in readable cookies.
Tooling: just a browser console, a public request catcher, and patience while reading ugly template code.
Goal: turn a boring reflected HTML injection into real script execution under CSP.
Main idea: I did not win with a normal onerror=alert(1) payload.
Real lesson: if the app gives you a weird internal script gadget, stop forcing classic XSS and start understanding the gadget.
This challenge is nice firstly because the AI was forbidden secondly because the bug is not hidden in ten frameworks or layers of infra. It is sitting there in plain sight. But the exploit only becomes obvious once you stop thinking "where can I put ?" and start thinking "what exact JavaScript path is already present in the page?"
This is the part that makes the challenge interesting:
web/views/paste.js
JS
<script nonce="${n}"> window.addEventListener("DOMContentLoaded", function() { var p = new URL(location.href).searchParams; var c = p.get("cb"); if (!c) return; var d = "}>-~" + c; var w = document.getElementById("content"); var el = w.lastElementChild; if (el && el.id === "quickpaste" && w.querySelector('.info[data-v="${n}"]')) { var n = el.lastElementChild; var raw = n.innerHTML.trim(); d = raw + d; } var s = document.createElement("script"); s.appendChild(document.createTextNode(d)); document.body.appendChild(s); });</script>
This is everything.
The page literally:
reads cb from the URL
builds JavaScript text from it
maybe prepends attacker-influenced HTML-derived text
appends a new <script> element
executes it from a nonce-trusted script context
So I stopped trying to bypass CSP manually.
The page already gives me the bypass.
const ALLOWED_HOSTNAMES = new Set(["web", new URL(APP_URL).hostname]);app.post("/report", async (req, res) => { const { url } = req.body; const parsed = new URL(url); if (!ALLOWED_HOSTNAMES.has(parsed.hostname)) { return res.status(400).send("URL must be on this domain."); }
And the bot enforces its own copy too:
bot/bot.js
JS
if (parsed.hostname !== ALLOWED_DOMAIN) { return res.status(400).json({ error: "URL must be on the challenge domain" });}
This is why the final working live payload had to use:
The page already contains a JavaScript execution gadget using the cb parameter. So the real task is to shape the DOM so that gadget builds valid JavaScript for me.
The bot visits my crafted page with the flag cookie already set. Because the cookie is same-origin and readable, my JavaScript can read document.cookie and leak it out.
malformed SVG/foreignObject turns garbage into a JS comment
the admin bot carries the flag in a readable cookie
same-origin JavaScript turns that into a full win
This is the kind of web challenge I like:
small codebase, tiny bug surface, but the exploit only clicks when you actually read the code instead of brute-forcing payloads.