Introduction
Cross-Site Scripting (XSS) lets an attacker run their own JavaScript inside a victim's browser, in the context of a trusted site. Because the malicious script executes as if it belongs to the legitimate site, it can steal session cookies, capture keystrokes, or silently perform actions on the victim's behalf.
Cricket analogy: XSS is like an impostor sneaking onto the field wearing an official team jersey: because he looks like a trusted player, he can walk into the dressing room and steal equipment unnoticed.
Explanation
XSS happens when untrusted input is rendered into a page as if it were trusted HTML/JavaScript instead of being treated as plain text. There are three main types. Stored XSS occurs when malicious input is saved on the server (e.g., in a comment or profile field) and then served to every user who views that page — it is the most dangerous because it affects many victims automatically. Reflected XSS occurs when malicious input is included in a request (commonly a URL parameter) and immediately echoed back in the response without proper encoding — it typically requires tricking a victim into clicking a crafted link. DOM-based XSS occurs entirely on the client side: JavaScript already running in the page takes data from an untrusted source (like location.hash or document.URL) and writes it into the DOM in an unsafe way, without the malicious payload ever necessarily touching the server at all.
Cricket analogy: Stored XSS is like a poisoned scoreboard entry that every viewer sees at the stadium; reflected XSS is like a rigged link in a fake ticket email that only fires when a fan clicks it; DOM-based XSS is like a scoreboard app's own display logic misreading a URL parameter and glitching without the stadium server ever being touched.
Example
# VULNERABLE: user-supplied comment is inserted into HTML unescaped
def render_comment_vulnerable(comment_text):
return f"<div class='comment'>{comment_text}</div>"
# If comment_text is <script>alert(1)</script> it executes in
# every visitor's browser who views the page (stored XSS).
# SECURE: output is HTML-escaped before insertion
from html import escape
def render_comment_secure(comment_text):
safe_text = escape(comment_text) # < > & " ' become entities
return f"<div class='comment'>{safe_text}</div>"
# The browser now renders the payload as inert text, not executable markup.
# Additionally, a Content-Security-Policy response header restricts
# which script sources the browser will execute at all:
# Content-Security-Policy: script-src 'self'Analysis
The primary defense against XSS is context-aware output encoding: any untrusted data written into HTML, an HTML attribute, JavaScript, or a URL must be escaped appropriately for that specific context before rendering, so the browser interprets it as inert data rather than executable code. Most modern web frameworks (React, Angular, Django templates, etc.) auto-escape output by default, which is why avoiding raw/'dangerouslySet' style APIs is important — those bypass the framework's built-in protection. A Content Security Policy (CSP) header is a critical second layer: it tells the browser which script sources are allowed to execute, so even if an attacker manages to inject a <script> tag, a well-configured CSP (e.g., disallowing inline scripts and restricting script-src to trusted origins) can prevent it from running. Additional defenses include the HttpOnly cookie flag (so injected JavaScript cannot read session cookies even if XSS occurs) and input validation as a supplementary layer, mirroring the same 'encode on output, validate on input, never rely on validation alone' principle used against SQL injection.
Cricket analogy: Context-aware output encoding is like a stadium announcer translating a fan's shouted message into the correct language for the scoreboard so it displays as harmless text rather than triggering an alarm.
Key Takeaways
- Stored XSS persists on the server and hits many victims; reflected XSS requires a crafted link per victim; DOM-based XSS happens entirely client-side.
- Context-aware output encoding is the primary defense — escape data for the context it's rendered into.
- Content Security Policy (CSP) is a critical second layer that restricts which scripts can execute.
- HttpOnly cookies prevent injected scripts from stealing session cookies.
- Modern frameworks auto-escape by default — avoid raw HTML injection APIs.
Practice what you learned
1. Which type of XSS persists on the server and affects every user who views the affected page?
2. Reflected XSS typically requires which condition to succeed?
3. DOM-based XSS is distinct because:
4. What is the primary defense against XSS?
5. What does a Content Security Policy (CSP) header accomplish?
Was this page helpful?
You May Also Like
OWASP Top 10 Overview
A practical tour of the OWASP Top 10, the industry-standard list of the most critical web application security risks.
Secure Coding Basics
Core secure coding practices: input validation, output encoding, least privilege, and never trusting client-side data.
Session Management Security
How to protect user sessions with secure cookie attributes and defenses against fixation and hijacking.
SQL Injection
How unsanitized input can hijack a database query, and why parameterized queries are the essential defense.