This article explains reflected cross-site scripting (XSS) through a practical lab where users inject JavaScript into a BookShop search box that returns unsanitized user input as HTML. Reflected XSS is the most reported web vulnerability in bug bounty programs, and understanding how it works is critical for both attackers and defenders. The lab demonstrates why input should never be inserted into a page as markup, and covers real-world attack patterns and defenses.
- How reflected XSS exploits unsanitized outputThe BookShop search page uses innerHTML to insert the user's search term directly into the page heading without escaping special characters. When a user searches for HTML markup instead of text, the browser interprets angle brackets as real tags and executes any embedded code with the site's full permissions. This happens because the browser cannot distinguish between markup the developer wrote and markup that came from the attacker's input.
- Why script tags fail but event handlers succeedBrowsers deliberately block script elements added through innerHTML as a built-in safety measure, so a simple `` injection will not work in this lab. However, event-handler attributes on HTML elements bypass this protection. An attacker can inject an image tag with a broken source that triggers an error handler containing JavaScript, which executes with the site's permissions when the load fails.
- Attack payloads differ by injection contextThe lab demonstrates that successful exploitation requires first identifying where user input lands in the page's HTML structure. Input can land inside a tag, inside an attribute, or inside JavaScript code, and each context requires a different payload construction approach. Testing with harmless markup first—like bold tags—helps confirm the injection point before attempting functional exploits.
- Real-world consequences of reflected XSSAttackers use reflected XSS to steal session data, perform actions as the victim, or display convincing fake login forms on the legitimate domain. Because the attacker's script runs with the site's full trust, it can read the page content, act as a logged-in user, and send sensitive data to external servers. This makes reflected XSS particularly dangerous for phishing and credential theft.
- Defenses require multiple layersThe article lists four key mitigation strategies: HTML-encoding user input before insertion, using safe DOM APIs like textContent instead of innerHTML, relying on frameworks like React and Vue that escape by default, and adding a Content Security Policy to block inline scripts and event handlers. The article explicitly warns that blocklists filtering the word 'script' are ineffective because attackers have dozens of alternative injection methods.
- Reflected versus stored XSS distinctionReflected XSS requires an attacker to send the victim a crafted link containing the payload in the URL, and the attack only affects that specific victim's browser. Stored XSS is saved by the server and runs for all users who view the affected page, making it more dangerous at scale. The lab focuses on reflected XSS because it represents the most common vulnerability reported in bug bounties.
- XSS ranks as top critical weaknessCross-site scripting appears as CWE-79 at the top of MITRE's 2024 CWE Top 25 list of the most dangerous software weaknesses and consistently ranks among the most reported bug classes on bug bounty platforms. This consistent prominence underscores why hands-on understanding of XSS mechanics is essential for cybersecurity professionals.
Bottom line: Reflected XSS exploits occur when user input is inserted into HTML as markup rather than text, allowing attackers to inject functional JavaScript that runs with the site's permissions. The fundamental defense is to always escape user input for its context, prefer safe APIs like textContent over innerHTML, and implement Content Security Policy as a secondary protection layer.
📋 In this walkthrough
What this lab teaches
Cross-site scripting (XSS) happens when a website puts your input into a page as markup instead of as text. Once your input is markup, it can contain elements and event handlers, and those run JavaScript in the page with the site’s full trust.
“Reflected” means the payload is not stored anywhere. It travels in the request, usually the URL, and comes straight back in the response. An attacker sends a victim a crafted link; the victim’s browser runs the attacker’s script on the real site.
How BookShop's search works
When you search, the page builds a heading like “Results for your query” and inserts it with innerHTML. Nothing is escaped, so angle brackets you type become real HTML tags. The lab is solved when your injected code calls the lab’s solve function, which proves arbitrary JavaScript ran.
The attack approach
- Search for a normal word first and find where it appears in the page. That spot is your injection point.
- Check whether your angle brackets come back as real HTML. Search for a word wrapped in bold tags and see if it renders bold.
- A plain script tag will not work here. Browsers deliberately do not run script elements added through
innerHTML. - Event-handler attributes are different. An element that fails to load, for example, fires its error handler, and that handler is JavaScript you control.
Full walkthrough (spoiler)
Stuck after a few attempts? Open the steps below. Solving it yourself first is worth more XP, and more learning.
Show the step-by-step solution
- Search for something harmless like book and confirm your query appears in the results heading.
- Search for an image tag whose source is deliberately broken, so it can never load.
- Give that image an error handler that calls the lab’s solve function, using the function name and flag shown in the lab objective.
- Submit the search. The broken image fails to load, the error handler fires, your JavaScript runs, and the lab marks you solved.
Why it works
The browser has no way to know which parts of the page the developer wrote and which parts came from your search box. Both arrive as one block of HTML. Your image tag is as real to the browser as the site’s own logo, so its error handler gets the same permissions as the site’s own code: it can read the page, act as the logged-in user and send data anywhere.
Where this happens in the real world
XSS (CWE-79) topped MITRE’s 2024 CWE Top 25 list of the most dangerous software weaknesses, and it is consistently among the most reported bug classes on bug bounty platforms. Reflected XSS is typically used to steal session data, perform actions as the victim, or show a fake login form on the real domain, which makes phishing far more convincing.
How to defend against it
- Escape output for its context. HTML-encode
<,>, quotes and ampersands before putting input into HTML. - Use safe DOM APIs.
textContentandsetAttributeinstead ofinnerHTML. - Let your framework do it. React, Vue and Angular escape by default; avoid their raw-HTML escape hatches.
- Add a Content Security Policy that blocks inline scripts and event handlers, as a second layer.
- Do not rely on blocklists that strip the word “script”. As this lab shows, there are dozens of other ways in.
Try it, then go further
Reading about it is the easy part. Open the lab, try your own approach before peeking at the spoiler, and see how many attempts it takes.
