Security
Cross-Site Scripting (XSS) — Stored, Reflected, DOM-Based
Master Cross-Site Scripting (XSS) vulnerabilities in JavaScript. Learn Stored, Reflected, and DOM-Based XSS attacks and prevention strategies.
1. Introduction
Cross-Site Scripting (XSS) is a security vulnerability that occurs when an application includes untrusted user input in a web page without proper validation or escaping, allowing attackers to execute malicious scripts in users' browsers.
2. Why It Matters
If an attacker successfully executes malicious scripts inside a user's browser, they can hijack user sessions, steal authentication cookies, record keystrokes (keylogging), or redirect users to malicious landing pages.
3. Real-World Analogy
Think of a Public Announcement Bulletin Board:
- Safe Postings: Users post cards containing text: "Lost cat - call 555." The board manager displays them safely.
- XSS Attack (Injecting active instructions): An attacker posts a card containing invisible chemical instructions: "When anyone reads this card, grab their wallet and throw it over the fence." The manager pins the card to the board. When visitors read the card, their brains execute the instructions, and they throw their wallets away. The attacker collects the wallets.
4. Types of XSS Attacks
XSS vulnerabilities are divided into three categories:
1. Stored XSS (Persistent):
The malicious script is saved on the server (like in a database comment field) and rendered to all users who visit the page:
2. Reflected XSS (Non-Persistent):
The script is contained in a URL parameter and reflected back to the user by the server without escaping:
3. DOM-Based XSS:
The vulnerability exists entirely in the client-side JavaScript. The script reads data from a user-controlled source (like location.hash) and writes it to an unsafe DOM sink (like innerHTML):
5. Prevention Strategies
To protect your application from XSS:
• Context-Aware Escaping: Convert special HTML characters into safe entities (e.g. < to <, > to >) before rendering user input.
• Avoid innerHTML: Use textContent or innerText instead of innerHTML, ensuring the browser treats input as text rather than executable HTML.
• Sanitize HTML: If you must support rich text input (like a markdown editor), run the input through a sanitization library like DOMPurify to strip out script tags and malicious attributes.
6. Practical Example
This script demonstrates how to escape HTML tags to render untrusted user input safely:
7. Common Mistakes
- Using regex replace rules for HTML sanitization: Writing custom regex replace rules to strip out script tags is easily bypassed by attackers (e.g. using uppercase tags
<SCRIPT>or nested structures<scr<script>ipt>). Always use established sanitization libraries.
8. Quick Quiz
Q1: Which type of XSS vulnerability occurs entirely in client-side JavaScript by writing input to unsafe sinks like innerHTML?
A) Stored XSS
B) DOM-Based XSS
Answer: B — DOM-Based XSS vulnerabilities reside entirely in the client-side JavaScript code when reading/writing unsafely to DOM nodes.
9. Scenario-Based Challenge
The Unsafe User Profile Bio Field:
A user profile bio page displays descriptions: bioContainer.innerHTML = user.bio. An attacker updates their bio to include malicious scripts. Refactor this rendering script to prevent DOM-based XSS attacks.
10. Debugging Exercise
Explain why this input validation helper can be bypassed, and how to fix it:
function cleanInput(val) { // Bug: only matches lowercase script tags once! return val.replace('<script>', '').replace('</script>', ''); }
// Show how an attacker bypasses this check: // Input: <SCRIPT>alert(1)</SCRIPT> or <img src=x onerror=alert(1)>
View Solution
Diagnosis: The check is case-sensitive, allowing attackers to use <SCRIPT>. It also fails to account for other executable tags (like <img onerror=...>, <iframe>, or <svg>).
Fix: Use a robust library like DOMPurify to sanitize HTML inputs, or use context-aware HTML escaping:
import DOMPurify from 'dompurify';
function cleanInput(val) { return DOMPurify.sanitize(val); // Safe and robust! }
11. Interview Questions
🟢 Q1: Compare Stored XSS, Reflected XSS, and DOM-Based XSS.
Answer:
• Stored XSS: The malicious payload is saved persistently on the database or server. It is executed whenever a user requests the saved resource.
• Reflected XSS: The payload is sent inside the request URL and reflected back immediately by the server in the response page.
• DOM-Based XSS: The vulnerability exists entirely on the client-side JavaScript. The script reads input from a source (like window hashes) and writes it directly to an unsafe DOM sink (like innerHTML).
12. Production Considerations
- • Use HttpOnly Cookies: Set the
HttpOnlyflag on authentication cookies. This prevents client-side scripts from reading the cookies viadocument.cookie, protecting user sessions from XSS token theft.