01 //Introduction
Cross-Site Scripting (XSS) remains one of the most common and dangerous web application vulnerabilities. According to the OWASP Top 10, XSS vulnerabilities continue to affect countless websites and applications, potentially exposing users to data theft, session hijacking, and malware distribution.
XSS has consistently appeared in the OWASP Top 10 since its inception, highlighting the persistent nature of this vulnerability class despite growing awareness.
This guide will help you understand how to identify XSS vulnerabilities during code review, explain how attackers exploit these weaknesses, and provide practical techniques to prevent them.
Which of the following best describes the main risk of XSS vulnerabilities?
02 //What is XSS?
Cross-Site Scripting (XSS) is a vulnerability that allows attackers to inject malicious scripts into web pages viewed by other users. These scripts execute in the victim's browser, with access to cookies, session tokens, and other sensitive information retained by the browser for that site.
Attacker submits a payload through any input the application reflects or stores.
The application emits the payload into HTML/JS without escaping or sanitization.
The victim's browser parses and runs the script in the page's origin.
XSS attacks occur when an application includes untrusted data in a webpage without proper validation or escaping.
Types of XSS Vulnerabilities
| Type | Description | Attack Vector |
|---|---|---|
| Reflected XSS | Script is reflected off a web server, such as in search results or error messages | URL parameters, form fields |
| Stored XSS | Malicious script is stored on the target server, such as in a database | Comments, user profiles, forums |
| DOM-based XSS | Vulnerability exists in client-side code rather than server-side | URL fragments, client-side storage |
Basic XSS Vulnerability Demo
03 //Finding XSS Sinks
XSS 'sinks' are places in your code where user-controlled input can be executed as code or rendered as HTML. During code review, identifying these sinks is the first step to finding potential XSS vulnerabilities.
1// DOM manipulation sinks
2element.innerHTML = userInput;
3element.outerHTML = userInput;
4document.write(userInput);
5document.writeln(userInput);
6
7// JavaScript execution sinks
8eval(userInput);
9setTimeout(userInput, 100);
10setInterval(userInput, 100);
11new Function(userInput);
12
13// HTML attribute sinks
14element.setAttribute('src', userInput);
15element.setAttribute('data', userInput);
16element.href = userInput; // Especially if it starts with 'javascript:'Frameworks often have their own sinks. For example, Angular's [innerHTML] binding, React's dangerouslySetInnerHTML, or jQuery's html() method can all introduce XSS vulnerabilities.
XSS Sink Identification
Drag each JavaScript function or property into the correct category: is it an XSS sink (dangerous) or safe?
Available Sinks
When reviewing code, search for these dangerous sinks and check if they are receiving user input directly or indirectly. Some sinks might be less obvious, such as template engines, dynamic script loading, or 3rd party libraries that manipulate the DOM.
04 //Identifying XSS Sources
XSS 'sources' are origins of untrusted data that could contain malicious content. Any input that can be controlled by an attacker is a potential XSS source.
When identifying sources, think about 'trust boundaries' - points where data passes from an untrusted zone to a trusted one. These are critical inspection points during code review.
Common XSS sources include:
- URL parameters and query strings
- Form inputs
- HTTP headers (like User-Agent, Referer)
- Cookies
- Post data
- Data from APIs or databases (if they store user-provided content)
- WebSocket messages
- URL fragments
- URL parameters ?search=<script>
- Form inputs <input> values
- HTTP headers User-Agent, Referer
- API responses JSON, GraphQL
- URL fragments location.hash
- element.innerHTML
- document.write()
- eval(...)
- element.setAttribute('src', ...)
- element.onclick = ...
Interactive Challenge
Which of these code snippets represents a potential XSS source?
05 //Tracing Data Flow
To find XSS vulnerabilities during code review, you need to trace how data flows from sources to sinks. This means following user input as it passes through your application.
A technique called 'taint analysis' helps track potentially dangerous input through your codebase. Modern static analysis tools can automate this process by following variables derived from user input.
Key questions to ask during code review:
- Where does the user input come from?
- How is the input transformed as it moves through the code?
- Is the input properly validated or sanitized before reaching a sink?
- Are there any bypasses or edge cases that could evade the protection?
Tracing Data Flow Exercise
Connect the source of untrusted data to the sink where it could lead to an XSS vulnerability.
Sources
Data Flow
Sinks
Modern applications often have complex data flows across multiple files, functions, and even services. Tools like static application security testing (SAST) can help identify potential data flows from sources to sinks.
06 //Prevention Techniques
Preventing XSS vulnerabilities requires a combination of defensive coding practices and security controls:
The most effective XSS defense strategy combines multiple layers of protection. No single approach is foolproof, so implementing several complementary techniques creates a more robust defense.
- Input Validation: Validate input against a whitelist of allowed values or patterns.
- Output Encoding: Encode special characters before inserting untrusted data into HTML, JavaScript, CSS, or URL contexts.
- Content Security Policy (CSP): Use HTTP headers to restrict which scripts can execute in your application.
- Use Safer APIs: Prefer safer alternatives like textContent over innerHTML.
- Sanitization Libraries: Use well-tested libraries for cleaning user input.
1// HTML context encoding
2const safeHtml = escapeHtml(userInput);
3element.innerHTML = safeHtml;
4
5// JavaScript context encoding
6const safeJs = JSON.stringify(userInput);
7element.onclick = function() { alert(safeJs); };
8
9// URL context encoding
10const safeUrl = encodeURIComponent(userInput);
11element.href = 'https://example.com/search?q=' + safeUrl;
12
13// CSS context encoding
14const safeCss = escapeCss(userInput);
15element.style = 'color: ' + safeCss;Fix the Vulnerable Code
Select the best approach to fix this XSS vulnerability:
Vulnerable Code:
function showProfile(username) {
// Display the username in the profile section
document.getElementById('profile-name').innerHTML = username;
}
Choose the best approach to fix this vulnerability:
Spot the XSS Sink
Each snippet contains one dangerous sink that lets attacker-controlled input become executable code. Click the line where the sink appears.
1function showGreeting(name) {2const container = document.getElementById('greeting');3const message = 'Hello, ' + name;4container.innerHTML = message;5}