What is Detectify?

Building an XSS polyglot through SWF and CSP

May 28, 2015

The mind twister was to abuse the CSP headers to inject a javascript through a third-party domain that only allowed SWF-upload. A few Payment Service Providers offers bug bounty programs. On one of the providers I was able to find a stored XSS on the receipt-page of a successful payment. The receipt page had a permalink-URL that was sent out by email to the buyer. This meant that the XSS could be accessed by anyone that had the receipt link.

Monday 12:04 AM. While doing some Sunday night research – also known as “bug bounty hunting” – I stumbled upon a fun challenge that I wanted to share. The mind twister was to abuse the CSP headers to inject a javascript through a third-party domain that only allowed SWF-upload.

A few Payment Service Providers offers bug bounty programs. On one of the providers I was able to find a stored XSS on the receipt-page of a successful payment. The receipt page had a permalink-URL that was sent out by email to the buyer. This meant that the XSS could be accessed by anyone that had the receipt-link. The receipt URL was on their main domain and triggered the XSS without any interaction.

But.

The site had a Content Security Policy in place, which did not allow inline script to run.

csp-damn3

The XSS worked in IE since the site wasn’t using the X-Content-Security-Policy-header. But that was it – IE only. That sucked. I wanted to give more punch in my bug report. There must be a way, I thought.

Monday 12:45AM. I emailed them and told them about the issue anyway, but I told them I also believed that I could go further with it.

Here’s how their CSP looked like (Yeah, I’ve changed the names here):

ontent-Security-Policy: default-src 'self' https://*.payment-provider.com; style-src 'unsafe-inline' 'self' https://*.payment-provider.com; script-src https://ads.cool-ad-platform.com https://stats.g.doubleclick.net https://ajax.googleapis.com/ajax/libs/swfobject/ https://www.googleadservices.com https://*.payment-provider.com 'self';

As you see, the script-src property actually include external sites. What if I was able to upload scripts to one of those domains instead? Then the CSP would accept the file and the XSS would run for everyone. Awez!

The one URL that caught my attention was the cool-ad-platform.com. I visited their page and noticed that they had a trial account registration.

Once registered I noticed I was able to upload files that would also be hosted on the ads.cool-ad-platform.com domain, nice!

But.

The file formats allowed was restricted to images and SWF-files. The problem was that I had to be able to upload a file that would be accepted as valid javascript. My first thought was to use a GIF/JS polyglot (that is both a working GIF and valid JS) since I’ve seen that worked out well before. The Bit by bit-guide of a GIF helped out good to figure out how to build it. The problem was that the ad-site had size-restrictions for the image being uploaded:

csp-image-restrictions

If you’ve tried to build a GIF-polyglot before (well, if you have, we are hiring!), you know that the part of the data in the GIF you need to modify to craft a working JS-file is the Logical Screen Descriptor using the canvas dimensions (right after GIF89a). In a GIF-polyglot you’re using the GIF89a header as a variable in Javascript, by simplifying it a bit, it looks like this:

GIF89a= 'MUMBOJUMBOBOGUSBACON';alert(1)//

This makes the image 8253 in width (and 8231 in height) since both the canvas width and height is two bytes long integers in little endian:

csp-gif-width

To lower the dimensions of a GIF to match the above dimension restrictions you ended up with control characters, and you know, javascript does not like control characters:

csp-gif-inline

 

csp-illegal-char

So close. Yet so far away.

I had to change plan. I noticed that SWF-files was also allowed. I tried to use a SWF with zlib/Huffman encoding but it wasn’t allowed. I finally realized that the site did not allow any compressed versions of SWFs, only files with FWS as the magic number (that is – without any compression). I found a nice guide of the SWF-FWS file format and started testing.

My plan was to do the same thing as with the GIF/JS-polyglot. Use the magic number as a variable name in javascript and then as soon as possible escape the header with a quote and then exit the quote later on and inject my payload. In pseudo code, that is:

FWS ='BACONBACON'-alert(1)//

The problem was that the ad-site did not only verify if the magic number was FWS (uncompressed), but it validated the complete header block.

Damn it.

The lowest control character allowed in javascript context (that is, not inside a string of some sort) is 0x09, the TAB-character. This was, according to the site’s validation, allowed inside the header block. I also used 0x2C and 0x27 (,') to separate the variable and escape the rest of the content inside a single quote. A bit far down I escaped the quote and inserted my payload:

csp-js-swf

I tried uploading it, and to my surprise:

csp-uploaded

The SWF was uploaded successfully. It was accepted by the ad-site’s SWF-validator as valid even though it cannot really run or do anything. Using my test page I then tried it out:

<script src="https://s3.amazonaws.com/detectify-labs/js.swf"></script>

But, the following error showed up:

csp-fws-not

 

In this case in the javascript I’m not setting the FWS variable, so the javascript expects it to exist already. By changing my test into:

<script id="FWS" src="https://s3.amazonaws.com/detectify-labs/js.swf"></script>

It actually worked!

csp-yiihaa

Monday 4:51AM. A nice little email was sent to the payment provider and I finally went to bed. They answered the same day and fixed it by removing the ad-site from the script-src property in the CSP. They actually fixed the XSS during my night crunch but I could at least show them that my payload would’ve worked.

You can find the SWF and an example here:
detectify-labs.s3.amazonaws.com/csp.html


Author:
Frans Rosén
Knowledge Advisor
@fransrosen