Cross-site scripting (XSS)
TL;DR: Attacker JavaScript runs in the victim’s browser, in the origin of the vulnerable app, with full access to that origin’s cookies, storage, and APIs.
What it is
The browser renders attacker data as code because the server or client mixes untrusted input into an HTML, JS, or attribute context without the right encoding. Three families: reflected (input from the current request), stored (persisted server-side and served to other users), and DOM-based (the sink is in client JS — see dom-xss).
Preconditions / where it applies
- A sink that emits attacker data into a page in a script-executable position: HTML body, attribute, JS block, URL handler, or via
innerHTML/document.write. - A browser without an effective Content-Security-Policy (content-security-policy-bypass), or with one that allows
'unsafe-inline'or wide allowlists. - The attacker can deliver the URL (reflected/DOM) or get content rendered to other users (stored).
Technique
- Find the context. Inject a unique marker (
xss7q) and look at the rendered HTML. Is it inside a tag, attribute, comment,<script>, or URL? -
Pick a payload that fits the context.
1 2 3 4 5 6 7 8
<!-- HTML body --> <svg onload=alert(1)> <!-- Single-quoted attribute --> ' autofocus onfocus=alert(1) x=' <!-- Inside a <script> string --> ';alert(1);// <!-- javascript: URL --> javascript:alert(1)
- Bypass filters. Mixed case (
<sCrIpT>), HTML entities, JS escapes,eval(atob('...')), template literal tricks. See waf-bypass and the cheat sheets below. - Escape sanitiser quirks. DOMPurify on outdated versions, server-side sanitisers that miss SVG/MathML, mutation-XSS where
innerHTMLre-parses your safe-looking output into something executable. - Weaponise. Read DOM and exfil sensitive data, mint same-origin requests (
fetch('/api/me',{credentials:'include'})), pivot to csrf now that SOP is on your side, inject a key-logger, install a service-worker-persistent-xss for persistence. - CSP bypass. Look for JSONP endpoints,
script-srcwith a CDN that hosts AngularJS or known gadget files, allowlisted nonceless'unsafe-eval', orbase-urinot locked → inject a<base>tag.
Detection and defence
- Contextual output encoding by default — template engines that escape based on sink (HTML, attr, JS, URL). Avoid
innerHTML, prefertextContent. - Strict CSP with a nonce per request; no
'unsafe-inline', no broad allowlists, lockbase-uri 'self', setobject-src 'none'. Add trusted-types-bypass resistance viarequire-trusted-types-for 'script'. - HttpOnly + Secure + SameSite on session cookies to blunt token theft.
- Detection: server-side reflection scanners in CI; CSP report-uri telemetry; WAF rules for canonical payloads; review for
dangerouslySetInnerHTMLand similar.
References
- PortSwigger — Cross-site scripting — labs, contexts, cheat sheet.
- OWASP — XSS Prevention Cheat Sheet — encoding by context.
- PayloadsAllTheThings — XSS Injection — payload reference.