Cross-Site Scripting (XSS)
Learn what Cross-Site Scripting (XSS) is, the different types (stored, reflected, DOM-based), how attacks are executed, practical mitigation techniques (escaping, CSP, safe APIs), and hands-on exercises to practice finding and fixing XSS.
What is Cross-Site Scripting (XSS)?
Cross-Site Scripting (XSS) occurs when an application includes untrusted user input in web pages without proper validation or escaping, allowing attackers to execute arbitrary JavaScript in other users' browsers. XSS is one of the most common and dangerous web vulnerabilities because it directly targets users, not just the server.
Why XSS Matters
- Steal session cookies or tokens (unless cookies are
HttpOnly). - Perform actions on behalf of users (CSRF-like effects combined with stolen auth).
- Build phishing UIs inside the app to capture credentials.
- Keylogging, content manipulation, or redirecting to malicious sites.
- Spread worms that propagate through stored content.
Types of XSS
Stored (Persistent) XSS
Malicious script is stored on the server (e.g., in a comment, profile, or message) and later served to users. This is high-impact because every visitor that loads the page executes the script.
Example Flow
Attacker posts <script>…</script> inside a comment. When other users view the comment, the script runs in their browsers.
Reflected (Non-persistent) XSS
Malicious input is reflected immediately in the response (e.g., search results page or error message). The attacker crafts a URL containing the payload and tricks a victim into clicking it.
Example Flow
The browser loads https://example.com/search?q=<script>alert(1)</script> and the application echoes q directly into the HTML without escaping.
DOM-based XSS
The vulnerability exists entirely in client-side JavaScript: the app reads an attacker-controlled value (location.hash, URL param, or document.cookie) and writes it to the DOM unsafely (e.g., innerHTML, document.write). The server may be safe, but the client-side code is not.
Example Flow
An attacker-controlled hash runs as HTML/JS.
Common XSS Injection Points
- User profile fields (name, bio)
- Comments and forum posts
- Search or feedback forms reflected back to the user
- Third-party widgets or editor content (WYSIWYG/HTML)
- Client-side code that writes user-controlled values into the DOM
Defenses against XSS
-
Contextual Output Escaping (primary defense) — always escape untrusted data based on where it's inserted.
- HTML Body: escape
< > &and quotes - HTML Attribute: escape quotes and ensure proper quoting
- JavaScript Context: escape or serialize safely (avoid string construction)
- URL Context: use
encodeURIComponent
Example: Server-side
Frameworks like React, Angular, and server-side templating engines usuall y provide automatic escaping for HTML contexts, prefer them over manual string concatenation.
- HTML Body: escape
-
Avoid Dangerous APIs
- Prefer
textContent,setAttribute, and DOM methods that treat input as text, not HTML. - Avoid
innerHTML,document.write,eval, andnew Functionwhere possible. - Safer:
el.textContent = userInput; - Unsafe:
el.innerHTML = userInput;
- Prefer
-
Content Security Policy (CSP)
- Use CSP to restrict where scripts/styles can be loaded from and to prevent inline script execution. A strong CSP significantly reduces impact even if XSS exists.
Example header:
- Use nonces for trusted inline scripts and avoid
'unsafe-inline'.
-
HTTPOnly Cookies
- Mark authentication cookies as
HttpOnlyto prevent JavaScript access. This mitigates the common goal of cookie theft via XSS (though not other attack effects).
- Mark authentication cookies as
-
Proper Input Validation & Sanitization (defense-in-depth)
- Validate inputs for expected formats (length, type). When you must allow HTML (e.g., rich text editors), sanitize it server-side using a well-maintained library (DOMPurify, sanitize-html) that removes dangerous tags/attributes while preserving safe markup.
-
Escape in templates, not at input time
- Prefer output escaping over input sanitization as the primary control, because the same input may be used in different contexts requiring different escaping rules.
DOM-based XSS — Deeper Dive
DOM XSS happens when client code directly places attacker-controlled data into the DOM without escaping.
Common Dangerous Patterns:
Safe Alternatives:
Watch out for third-party libraries or widgets that manipulate the DOM, they can introduce DOM XSS vulnerabilities.
Examples of Vulnerabilities & Fixes
Stored XSS (vulnerable)
User-submitted comments are saved and later rendered as-is.
Fix: escape on render, and sanitize rich-text inputs with a library that strips <script> and dangerous attributes like onerror.
Reflected XSS (vulnerable)
Fix: escape q before rendering or use a templating engine that auto-escapes.
Testing for XSS
- Manual Testing: inject payloads like
<script>alert(1)</script>,<img src=x onerror=alert(1)>, or JS URI payloads. - Automated Scanners: OWASP ZAP, Burp Suite, Arachni.
- Use Browser DevTools: to inspect DOM and see whether payloads are treated as HTML or text.
- Test DOM sinks (innerHTML, setAttribute, location.* usage) in client code.
Conclusion
Cross-Site Scripting (XSS) is a critical web security vulnerability that allows attackers to inject malicious scripts into web pages viewed by other users. XSS can lead to theft of sensitive information, account hijacking, defacement of websites, and spread of malware.
Preventing XSS requires a combination of secure coding practices, such as proper input validation, output encoding, and the use of security headers like Content Security Policy (CSP).
In short: never trust user input directly in the DOM without proper escaping or sanitization.