Cross-Site Scripting (XSS) Attacks

Mediumβ€’

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 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

β€’Convert < to &lt;
β€’Convert > to &gt;
β€’Convert & to &amp;
β€’Convert " to &quot;
β€’Convert ' to &#039;

Example Implementation:

function escapeHtml(text) {
  const map = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;'
  };
  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

β€’Prevent inline script execution
β€’Report violations for monitoring
β€’Control which domains can load 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

MethodEffectivenessComplexityPerformance ImpactUse Case
Input Sanitization⭐⭐⭐⭐ HighMediumLowServer-side validation
Output Encoding⭐⭐⭐⭐⭐ HighestLowNoneClient-side rendering
CSP⭐⭐⭐⭐ HighLowNoneDefense in depth
Framework Protection⭐⭐⭐⭐⭐ HighestVery LowNoneReact, 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 database

3. 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

β€’βŒ Trusting client-side validation alone
β€’βœ… Always validate and sanitize server-side

Mistake 2: Using innerHTML with user data

β€’βŒ element.innerHTML = userInput
β€’βœ… element.textContent = userInput

Mistake 3: Not implementing CSP

β€’βŒ Relying only on sanitization
β€’βœ… Use CSP as defense in depth

Mistake 4: Allowing 'unsafe-inline' in CSP

β€’βŒ script-src 'self' 'unsafe-inline'
β€’βœ… Use nonces or external scripts only

Key Takeaways

1Never trust user input - always sanitize
2Use textContent instead of innerHTML when possible
3Implement Content Security Policy (CSP)
4Validate and sanitize on both client and server
5Use framework's built-in escaping mechanisms