XSS Review & Practice
A curated set of progressively harder XSS challenges against fresh endpoints. Reflected, stored, DOM, blind — no hand-holding. The XSS course final.
A curated set of progressively harder XSS challenges against fresh endpoints. Reflected, stored, DOM, blind — no hand-holding. The XSS course final.
This is the XSS course final. Five challenges that combine reflected, stored, DOM, and blind XSS techniques. No hints beyond what is in the challenge description — you will need to identify the injection context, choose the right payload, and prove that the payload executes.
The five challenges below test every XSS technique covered in this course. Each challenge presents a vulnerable endpoint, a code snippet showing where the injection lives, and a pass/fail test. Your payload must pass the test — for example, executing alert(document.domain) or triggering a simulated callback to a blind XSS listener.
The challenges progress from a simple reflected XSS (challenge one) to a blind XSS against a simulated admin panel (challenge five). You will need to think about context: is the injection in an HTML element body, an attribute value, a JavaScript string, or a URL? The correct payload depends on the answer.
Work through each challenge in order. Type your payload into the input and press Submit. The simulator tests whether the payload achieves the required effect — for example, popping an alert in the simulated browser or sending a callback to the listener endpoint.
The search endpoint echoes your query directly into a <p> element. No escaping, no filter.
router.get("/search", (req, res) => {
res.send(`<p>You searched for: ${req.query.q}</p>`);
});Make the browser execute alert(document.domain) on the search results page.
Every major bug bounty platform — HackerOne, Bugcrowd, Intigriti — uses the same format as these challenges. The programme provides a target endpoint, the researcher identifies the injection context, and submits a proof-of-concept payload. The best reports include the vulnerable code snippet, the working payload, and a screenshot of the alert box or exfiltration attempt.
Blind XSS in particular is a staple of high-severity bug bounty findings. Many applications have stored XSS that only executes in an admin panel — the researcher must deliver the payload through a customer-support ticket, a user-report form, or a profile field that an administrator later views. The callback URL confirms execution.
Defending against all five challenge types requires the same layered approach: context-aware escaping at every output point, a strict CSP that disables inline scripts, and automated scanning that catches DOM sinks before they reach production. Blind XSS is the hardest to defend against because the vulnerable code may only execute in an internal panel that the security team does not routinely scan.
// VULNERABLE — reflected HTML context
router.get('/search', (req, res) => {
res.send('<p>You searched for: ' + req.query.q + '</p>');
});
// SAFE — escape at the output layer
router.get('/search', (req, res) => {
const q = escapeHtml(req.query.q ?? '');
res.send('<p>You searched for: ' + q + '</p>');
});
// VULNERABLE — DOM hash → innerHTML
const tab = location.hash.slice(1);
document.getElementById('tab-content')!.innerHTML = tabs[tab];
// SAFE — allowlist the sink value
const tab = location.hash.slice(1);
const allowed = ['profile', 'settings', 'billing'];
const content = allowed.includes(tab) ? tabs[tab] : tabs['default'];
document.getElementById('tab-content')!.textContent = content;
// Blind XSS defence — strict CSP + input validation
// If the admin panel renders user content, use sandboxed iframes
// or a separate origin that cannot access admin cookies.1.What is the correct payload for an HTML-context reflected XSS where the input is echoed inside <p>INPUT</p>?
2.What distinguishes blind XSS from reflected XSS?
3.In the DOM hash challenge, why does the server-side scanning tool fail to detect the vulnerability?