Lab Difficulty: Expert
Topic: Cross-Site Scripting (Refined)
The objective of this lab is to perform a Reflected Cross-Site Scripting (XSS)
attack. The injection point is inside a JavaScript object within a javascript:
URL scheme (specifically, a fetch request). The application employs a Web
Application Firewall (WAF) that blocks specific characters (such as parentheses (
) and spaces), making standard function calls impossible. The goal is to bypass
these filters and execute the alert(1337) function.
Identification of Reflection Point
We began by analysing the “Back to Blog” link functionality on a post page. The HTML source code revealed the following structure for the link:
<a href="javascript:fetch('/analytics', {method:'post',body:'/post?postId=2'}).finally(...)">Back to Blog</a>
The server reflects the current URL query parameters directly inside the
body property string of the fetch options object.
Vulnerability Assessment
The input is reflected inside a single-quoted string within a JavaScript
object literal: body:'/post?postId=INPUT'
To exploit this, we need to:
Break Out of String: Use a single quote ' to close the body value.
Break Out of Object: Use a closing brace } to close the fetch options object.
Inject Code: Insert our payload as a new argument to the fetch function.
Constraints (WAF): Through manual testing, we determined that the application blocks:
Parentheses ( and ): This prevents standard calls like alert(1337).
Spaces %20: This prevents separation of keywords like throw 1337.
The Bypass Strategy
To overcome the WAF restrictions, we employed a “Polyglot” strategy utilising JavaScript error handling and arrow functions.
Statement vs. Expression: We cannot inject a statement like throw
directly as a function argument. We must wrap it in an Arrow Function
x => { ... } to make it a valid expression.
Space Bypass: We used block comments /**/ as a separator instead
of spaces.
Parenthesis Bypass (Exception Handler): We used onerror=alert; throw
1337. By throwing an exception, the browser automatically passes the
error value (1337) to the assigned error handler (alert), triggering
the alert without using parentheses.
Execution Trigger (toString): We assigned our malicious arrow
function x to the toString property of the window. We then forced a
string conversion using window + ''. This implicitly calls
window.toString(), which executes our malicious function.
Payload Construction
The Payload:
&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window%2b'',{x:'