Bypassing WAFs with SVG


October 13, 2014

By Julian Berton (LinkedIn)

Recently, I presented a lightning talk at Ruxcon 2014, on a cross-site scripting issue we discovered on a client engagement, and two interesting ways in which we could bypass the WAF present (as well as Firefox’s cross-site scripting filter).

The cross-site scripting issue we found was fairly standard at first, with an initial URI like the following:

localhost:4000/apply_thankyou?uuid=d77a9190-4ace-11e4-b775-bd2f6eee9714&userId=542e239cc6f6f28004c4dae0&result=HC999|SUCCESS

This generates a page like the screenshot below, with the reference number pulled from a vulnerable parameter in a URI, with the “jquery.query.get()” function.

xss_blogpost_Image1

After some further investigation, we found the code behind this vulnerability as follows:

Untitled

Note the stages of this vulnerability – “jquery.query.get()” is used to retrieve the user’s reference number from the URI, the actual reference number is extracted through a string split, and then this is passed to the append() function, writing it to a page without filtering.

A simple test case with the <u> tag works, but <script> causes an HTTP 403 response, indicating the presence of a WAF. Interestingly enough, the “img” tag was not blocked (who knows when the application might need to supply an image as the reference number, clearly), and as it turns out, this is enough to trigger JavaScript execution.

Typically, the “src” parameter of an image tag will point to an image file, but it can also point to a data URI. A Data URI, supported by pretty much all modern browsers, is a way of inlining data in base64 format directly into a URI. In this case, we can inline a complete SVG file – an SVG file containing JavaScript, which again, most modern browsers will support:

blog-image2

(the base64 part decodes to the following):

blog-image3

This simple test case isn’t enough, however – in this example, our JavaScript only executes when a user views the image directly. Instead, we turn to a bit of JavaScript trickery to make this execute directly, modifying the payload thus:

blog-stage2xss-1

Again, the base64 component inside the SVG decodes as follows:

blog-stage2xss-2

In effect, this causes the outer SVG to load the inner SVG, incidentally causing the user’s browser to execute the JavaScript in the inner SVG file. More information on this technique can be found here:

http://insert-script.blogspot.com.au/2014/02/svg-fun-time-firefox-svg-vector.html

As it turns out, we didn’t have to do any of this. The “jquery.query.get()” function, used to fetch the request acknowledgement number, executes on a user’s browser, and can accept input from URL fragment identifiers.

These fragment identifiers aren’t subject to the interference of a WAF (since they never go to the server) – thus, we can modify our proof-of-concept URI as follows, to make it work locally against a user’s browser:

localhost:4000/apply_thankyou#uuid=d77a9190-4ace-11e4-b775-bd2f6eee9714&userId=542e239cc6f6f28004c4dae0&result=<B>HC999</B><script>alert(1);</script>|SUCCESS

Note that the question mark in the original URI is now a hash – it’s that simple.

The implications of this are fairly interesting, especially as more and more applications move towards a JavaScript-driven single-page model. It is important to note the security implications of such a model, especially when it comes to client-side attacks such as cross-site scripting, which may incidentally become suddenly far easier to exploit.

I’ll put up a more detailed blog post ~soon~, with a little more background to this vulnerability, as well as how we managed to identify this issue, so watch this space.

For now, you can download my Ruxcon slides here.

Leave a Reply

Your email address will not be published. Required fields are marked *