Cross-Site Scripting (XSS) Attacks
XSS attacks are one of the most common web vulnerabilities. Understanding how they work and how to prevent them is crucial for secure web development.
Quick Navigation: Understanding XSS Attack Process β’ XSS Prevention Strategies β’ XSS Prevention Methods Comparison β’ Best Practices for Implementation
Quick Decision Guide
Quick Prevention Checklist:
Never do this: element.innerHTML = userInput - allows XSS injection.
Always do this: - Use element.textContent = userInput (escapes automatically) - Or sanitize: DOMPurify.sanitize(userInput) if you need HTML - React escapes by default: <div>{userInput}</div> is safe
Server-side: Validate and sanitize ALL user input. Use libraries like validator.js or framework sanitizers.
CSP Header: Add Content-Security-Policy: script-src 'self' to prevent inline script execution.
Common Vulnerabilities: - Comment forms without sanitization - URL parameters reflected in HTML - eval() or innerHTML with user data - Third-party widgets without CSP
Test: Try injecting <script>alert('XSS')</script> in forms - if it executes, you have a vulnerability.
Understanding XSS Attack Process
Overview of XSS Attacks
Cross-Site Scripting (XSS) is a security vulnerability that allows attackers to inject malicious scripts into web pages viewed by other users. These scripts execute in the victim's browser with the privileges of the website.
Types of XSS Attacks
Figure 1: XSS Attack Types Comparison
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β XSS Attack Types β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Stored XSS (Persistent) β
β βββββββββββ βββββββββββ βββββββββββ β
β β Attackerβββββ>β Databaseβββββ>β Victim β β
β β Input β β Stores β β Views β β
β βββββββββββ βββββββββββ βββββββββββ β
β Script Script Script β
β Injected Stored Executes β
β β
β Reflected XSS (Non-Persistent) β
β βββββββββββ βββββββββββ βββββββββββ β
β β Attackerβββββ>β URL βββββ>β Victim β β
β β Sends β β Containsβ β Clicks β β
β βββββββββββ βββββββββββ βββββββββββ β
β Link Script Script β
β Reflected Executes β
β β
β DOM-based XSS β
β βββββββββββ βββββββββββ β
β β Attackerβββββ>β Client β β
β β Input β β DOM β β
β βββββββββββ βββββββββββ β
β Script Script β
β Injected Executes β
β (No Server) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββStored XSS Attack Flow
Figure 2: Stored XSS Attack Process
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Attacker β β Website β β Victim β
β β β Server β β Browser β
ββββββββ¬βββββββ ββββββββ¬ββββββββ ββββββββ¬βββββββ
β β β
β 1. Submit Comment β β
β <script>alert() β β
ββββββββββββββββββββββββ> β
β β β
β β 2. Store in DB β
β β (No Sanitization) β
β β β
β β β
β β 3. Victim Requests β
β β Page β
β β <βββββββββββββββββββββββ
β β β
β β 4. Return HTML β
β β (Script Included) β
β ββββββββββββββββββββββββββ>
β β β
β β β 5. Script
β β β Executes
β β βStep-by-Step Process:
1. Attacker injects malicious script into user input (comment, profile, etc.)
- Example: <script>document.cookie</script>
- Script contains malicious JavaScript code
2. Website stores the input without sanitization
- Input saved to database as-is
- No HTML escaping or validation
3. Victim views the page containing the malicious script
- Page loads from database
- Malicious script included in HTML
4. Script executes in victim's browser
- Runs with website's privileges
- Can steal cookies, session tokens, or perform actions
Reflected XSS Attack Flow
Figure 3: Reflected XSS Attack Process
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Attacker β β Website β β Victim β
β β β Server β β Browser β
ββββββββ¬βββββββ ββββββββ¬ββββββββ ββββββββ¬βββββββ
β β β
β 1. Create Malicious β β
β URL with Script β β
β example.com?q= β β
β <script>alert()</> β β
β β β
β 2. Send Link to β β
β Victim β β
ββββββββββββββββββββββββββββββββββββββββββββββββββ>
β β β
β β β 3. Victim
β β β Clicks
β β β Link
β β <βββββββββββββββββββββββ
β β β
β β 4. Return HTML β
β β (Script Reflected) β
β ββββββββββββββββββββββββββ>
β β β
β β β 5. Script
β β β Executes
β β βDOM-based XSS Attack Flow
Figure 4: DOM-based XSS Attack Process
βββββββββββββββ ββββββββββββββββ
β Attacker β β Victim β
β β β Browser β
ββββββββ¬βββββββ ββββββββ¬ββββββββ
β β
β 1. Create Malicious β
β URL β
β example.com# β
β <script>alert()</> β
β β
β 2. Send Link β
ββββββββββββββββββββββββ>
β β
β β 3. JavaScript Reads
β β URL Fragment
β β
β β 4. Injects into DOM
β β document.write()
β β innerHTML
β β
β β 5. Script Executes
β βXSS Prevention Strategies
Defense in Depth Approach
Multiple layers of protection:
1. Input validation and sanitization
2. Output encoding
3. Content Security Policy (CSP)
4. Framework protections
Figure 5: XSS Prevention Layers
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β User Input β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββ
β Layer 1: Input Validation β
β - Whitelist allowed chars β
β - Reject suspicious input β
βββββββββββββββββ¬ββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββ
β Layer 2: Input Sanitization β
β - Escape HTML entities β
β - Remove script tags β
βββββββββββββββββ¬ββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββ
β Layer 3: Output Encoding β
β - textContent (not innerHTML)β
β - Framework auto-escaping β
βββββββββββββββββ¬ββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββ
β Layer 4: Content Security β
β Policy (CSP) β
β - Block inline scripts β
β - Restrict script sources β
βββββββββββββββββ¬ββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββ
β Safe Output β
βββββββββββββββββββββββββββββββββInput Sanitization
Escape HTML Entities
< to <> to >& to &" to "' to 'Example Implementation:
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}Use Text Content, Not innerHTML
Dangerous (Vulnerable to XSS):
// β NEVER DO THIS
element.innerHTML = userInput;
document.write(userInput);
element.outerHTML = userInput;Safe Alternatives:
// β
SAFE
element.textContent = userInput;
element.innerText = userInput;
// β
React auto-escapes
<div>{userInput}</div>
// β
If HTML needed, sanitize first
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);Content Security Policy (CSP)
Restrict sources of executable scripts
Example CSP Header:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;XSS Prevention Methods Comparison
| Method | Effectiveness | Complexity | Performance Impact | Use Case |
|---|---|---|---|---|
| Input Sanitization | ββββ High | Medium | Low | Server-side validation |
| Output Encoding | βββββ Highest | Low | None | Client-side rendering |
| CSP | ββββ High | Low | None | Defense in depth |
| Framework Protection | βββββ Highest | Very Low | None | React, Vue, Angular |
Best Practices for Implementation
Server-Side Best Practices
1. Validate All Input
// Validate input type and format
if (typeof userInput !== 'string') {
throw new Error('Invalid input type');
}
// Whitelist approach (better than blacklist)
const allowedChars = /^[a-zA-Z0-9s.,!?-]+$/;
if (!allowedChars.test(userInput)) {
throw new Error('Invalid characters');
}2. Sanitize Before Storage
const validator = require('validator');
const sanitized = validator.escape(userInput);
// Store sanitized version in database3. Use Parameterized Queries
// β
SAFE - Parameterized query
db.query('SELECT * FROM users WHERE id = ?', [userId]);
// β VULNERABLE - String concatenation
db.query('SELECT * FROM users WHERE id = ' + userId);Client-Side Best Practices
1. Use Framework Auto-Escaping
// β
React - Auto-escapes
function Comment({ text }) {
return <div>{text}</div>;
}
// β
Vue - Auto-escapes
<template>
<div>{{ userInput }}</div>
</template>2. Prefer textContent Over innerHTML
// β
SAFE
const div = document.createElement('div');
div.textContent = userInput;
document.body.appendChild(div);
// β VULNERABLE
document.body.innerHTML = userInput;3. Sanitize If HTML Needed
import DOMPurify from 'dompurify';
// β
SAFE - Sanitize before using innerHTML
element.innerHTML = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
ALLOWED_ATTR: []
});Common Mistakes to Avoid
Mistake 1: Only sanitizing on client-side
Mistake 2: Using innerHTML with user data
element.innerHTML = userInputelement.textContent = userInputMistake 3: Not implementing CSP
Mistake 4: Allowing 'unsafe-inline' in CSP
script-src 'self' 'unsafe-inline'