Residential Proxies Web Scraping API Turn Sites Into AI Data Automate 3000+ Apps Learn Python Automation Pay As You Go Proxies
Residential Proxies Web Scraping API
Pay As You Go Proxies 10 Free Proxies Antidetect Browser No Code Browser Bots Web Data For AI Agents Hire Scraper Builders

How to Test for Cross-Site Scripting (XSS): Payloads, Contexts, and Tools

Updated August 2026
Cross-site scripting (XSS) testing checks whether a web application allows attacker-controlled scripts to execute in other users' browsers. Testing involves submitting script payloads through inputs, analyzing where and how the input appears in the response, and determining whether the browser interprets it as executable code. This guide covers the three XSS types (reflected, stored, and DOM-based), context-specific payloads that match how the input is rendered, automated scanning approaches, and the Content-Security-Policy defenses that should block XSS even when output encoding fails.

XSS is a client-side vulnerability: the attack executes in the victim's browser, not on the server. When an application renders user input as HTML without proper encoding, an attacker can inject JavaScript that runs with the same permissions as the legitimate page. This allows stealing session cookies, redirecting users to phishing pages, modifying page content, and making authenticated requests on the victim's behalf. XSS appears in the OWASP Top 10 under A03 (Injection) and remains one of the most frequently reported vulnerability types in bug bounty programs.

Effective XSS testing requires understanding HTML rendering contexts. The same input appears differently depending on whether it lands in the HTML body, an HTML attribute, a JavaScript string, a CSS value, or a URL. Each context requires different characters to escape and different payloads to trigger execution. A payload that works in an HTML body context does nothing in a JavaScript string context, and vice versa. Context-aware testing is what separates thorough XSS assessment from superficial scanning.

Map Input and Output Points

Before testing, identify every location where user input appears in the application's HTML responses. The input source might be a form field, a URL parameter, a cookie value, an HTTP header, or data previously stored in a database (profile name, comment text, uploaded filename).

Submit a unique, identifiable string (like xsstest12345) through each input and search for it in the response HTML. Use your browser's developer tools (View Source, Elements panel) to find every location where the string appears. Document the exact HTML context for each occurrence: is it inside a paragraph tag, inside an attribute value, inside a script block, or inside a URL?

Check both the immediate response (reflected XSS) and pages where stored data is displayed (stored XSS). If you submit data through a registration form, check where that data appears on profile pages, admin dashboards, email notifications, and anywhere else stored data is rendered. Stored XSS is more dangerous because every user who views the affected page is attacked, not just the user who clicks a crafted link.

For API responses, check whether input appears in JSON or HTML error messages that the frontend renders. Modern single-page applications often render API response data directly into the DOM, and if the API returns unsanitized user input, the frontend might render it as HTML even though the API itself is not "vulnerable."

Determine the Output Context

The output context determines which payloads can trigger script execution and which characters need to be present. There are five primary contexts in HTML.

HTML body context: The input appears between HTML tags, such as inside a <p>, <div>, or <span>. Example: <p>Search results for: [input]</p>. In this context, the attacker needs to inject an HTML tag that executes JavaScript, like <script> or an element with an event handler.

HTML attribute context: The input appears inside an HTML attribute value. Example: <input value="[input]">. The attacker needs to close the attribute (with a quote), optionally close the tag, and inject a new element or event handler. If the attribute is an event handler itself (onmouseover, onclick), the input is already in a JavaScript execution context.

JavaScript context: The input appears inside a JavaScript block or inline script. Example: <script>var name = '[input]';</script>. The attacker needs to close the string (with a quote) and break out of the statement to inject executable code.

URL context: The input appears in a URL, typically in an href or src attribute. Example: <a href="[input]">. The attacker can inject a javascript: URL scheme that executes when the link is clicked.

CSS context: The input appears inside a style attribute or stylesheet. Example: <div style="color: [input]">. CSS-based XSS is less common in modern browsers but historically allowed script injection through expressions and behavior properties.

Submit Context-Specific Payloads

HTML body payloads: Start with a simple tag injection. Submit an input containing angle brackets and an HTML tag. If you see the rendered tag in the page (not as text but as actual HTML structure), the application does not encode output in this context. Progress to tags with event handlers that do not require user interaction for more reliable detection.

HTML attribute payloads: The goal is to break out of the attribute context. If the input appears inside a double-quoted attribute, submit a payload that closes the quote and adds a new attribute or tag. Test whether the application encodes quotes within attribute values. Try both double quotes and single quotes because the attribute might use either delimiter.

JavaScript context payloads: Close the JavaScript string and inject new code. If the input appears in a single-quoted string, submit a payload that closes the quote and adds a new statement. If the application escapes single quotes, try alternative techniques: breaking out with backslash-escaping tricks, using template literals if the code uses backticks, or injecting into the JavaScript without quotes using arithmetic or string operations.

URL context payloads: Test whether the application accepts javascript: protocol URLs. Submit javascript: followed by a simple function call as a URL parameter that appears in an href attribute. Also test data: URLs with base64-encoded content. Modern browsers block some of these in certain contexts, so test in the browser that the application's users actually use.

For all contexts, test with encoding variations. URL-encode the payload characters (%3C for <, %22 for "). HTML-encode them (&lt; for <). Double-encode them (%253C). Some applications decode input multiple times or decode at different layers (URL decoding in the web server, HTML decoding in the template engine), and payloads that are blocked in plain text may pass through when encoded.

Test for DOM-Based XSS

DOM-based XSS occurs entirely in the browser without any server involvement. Client-side JavaScript reads input from a source (URL hash, query string, document.referrer, window.name, localStorage) and writes it to a sink (innerHTML, document.write, eval, setTimeout with a string argument) without sanitization.

Identifying DOM XSS requires reviewing the application's JavaScript code rather than just testing inputs and outputs. Look for code patterns where URL-derived values flow into DOM manipulation functions. Specifically look for document.location, window.location.hash, location.search, and document.referrer being used with innerHTML, outerHTML, document.write, or jQuery's .html() method.

Playwright is particularly useful for DOM XSS testing because it can execute JavaScript in the page context to inspect how the DOM was modified. After loading a page with a test payload in the URL, use page.evaluate() to check whether the payload was interpreted as HTML in the DOM rather than rendered as text. Playwright can also intercept console errors and CSP violations that indicate the payload was blocked by security policies.

Automated DOM XSS detection is harder than server-side XSS because the vulnerability is in client-side code, not in server responses. OWASP ZAP's AJAX Spider helps because it executes JavaScript while crawling, but dedicated client-side analysis tools like DOM Invader (part of Burp Suite) provide more targeted DOM XSS detection.

Automate with ZAP and Browser Tools

OWASP ZAP includes extensive XSS testing in its active scanner. It submits hundreds of XSS payloads to every parameter captured during spidering, testing multiple contexts and encoding variations. ZAP's XSS detection compares responses to determine whether payloads are reflected without encoding, and it flags findings by confidence level so testers can prioritize manual verification.

For more thorough testing, Burp Suite Professional's scanner handles complex XSS scenarios including payloads that require multiple encoding steps, attribute context breakouts, and JavaScript string escapes. Its scan result explains the exact context and why the payload would execute, reducing false positive investigation time.

Browser developer tools complement automated scanners. After submitting a payload, inspect the Elements panel to see how the browser parsed the response. If your payload appears as HTML elements rather than escaped text, XSS is confirmed. The Console panel shows JavaScript errors that payloads might trigger, and the Network panel shows whether CSP headers are present.

For systematic testing of many inputs, combine ZAP's broad scanning with Playwright tests that target specific high-risk inputs with context-appropriate payloads. ZAP finds the obvious cases across the entire application, Playwright verifies the nuanced cases where context matters.

Verify CSP Protection

Content-Security-Policy (CSP) is a defense-in-depth mechanism that instructs the browser to restrict which scripts can execute on the page. A well-configured CSP blocks XSS even when the application fails to encode output, because the browser refuses to execute inline scripts or scripts from unauthorized sources.

Check whether the application sends a CSP header by inspecting the response headers for any page. Look for the Content-Security-Policy header (the enforcement header) and Content-Security-Policy-Report-Only (the monitoring header that logs violations without blocking). If neither is present, XSS payloads face no browser-level barrier to execution.

Evaluate the CSP's strength. A strong CSP includes script-src with specific sources (no 'unsafe-inline', no 'unsafe-eval', no wildcard *). The presence of 'unsafe-inline' in the script-src directive defeats the primary purpose of CSP because it allows the browser to execute inline scripts, which is exactly what XSS payloads inject. The presence of 'unsafe-eval' allows eval() and similar functions, enabling another class of XSS. The presence of a wildcard (*) allows scripts from any source.

Test the CSP by injecting a payload and checking whether the browser blocks it. Playwright can listen for SecurityPolicyViolation events in the page, confirming that the CSP blocked the payload. If the payload executes despite a CSP header being present, the policy has a bypass (usually 'unsafe-inline' or an overly permissive source list).

Report CSP findings alongside XSS findings. An XSS vulnerability with no CSP is critical. An XSS vulnerability with a strong CSP that blocks execution is still a bug that needs fixing, but the CSP reduces the immediate risk.

XSS Prevention for Developers

The primary defense against XSS is context-aware output encoding. Every piece of user data rendered in HTML must be encoded for the context where it appears. In HTML body context, encode < > & " '. In HTML attribute context, encode the same characters plus the quote delimiter. In JavaScript context, use JSON encoding or JavaScript string escaping. In URL context, use URL encoding.

Modern template engines (React JSX, Angular templates, Jinja2 with autoescape, Go html/template) encode output by default, making XSS much harder to introduce accidentally. The vulnerability appears when developers bypass the encoding with raw HTML rendering (dangerouslySetInnerHTML in React, [innerHTML] in Angular, |safe in Jinja2). Security testing should specifically target inputs that pass through raw rendering functions.

CSP provides the second layer of defense. Even if encoding fails, a strict CSP that prohibits inline scripts prevents most XSS payloads from executing. Implementing CSP alongside output encoding means both must fail for an XSS attack to succeed.

Key Takeaway

Effective XSS testing requires identifying the HTML context where user input appears and submitting payloads designed for that specific context. Test for reflected, stored, and DOM-based XSS across every input point, automate broad scanning with ZAP, and verify that Content-Security-Policy headers provide defense in depth against any encoding failures.