Content uploaded to Facebook is stored on their CDN, which is served via various domains (most of which are sub-domains of either
The captioning feature of Videos also stores the
.srt files on the CDN, and I noticed that right-angle brackets were un-encoded.
I was trying to think of ways to get the file interpreted as HTML. Maybe MIME sniffing (since there’s no
It’s actually a bit easier than that. We can just change the extension to
.html (which probably shouldn’t be possible…).
Unfortunately left angles are stripped out (which I later found out was due to @phwd’s very much related finding), so there’s not much we can do here. Instead, I looked for other files which could also be loaded as
A lot of the photos/videos on Facebook now seem to contain a hash in the URL (parameters
__gda__), which causes an error to be thrown if we modify the file extension.
Luckily, advert images don’t contain these parameters.
If we try to blindly insert a string into an image and upload it we receive an error.
PNG IDAT Chunks
I started searching for ideas and came across this great blog post: “Encoding Web Shells in PNG IDAT chunks”. This section of this bug is made possible due that post, so props to the author.
The post describes encoding data into the IDAT chunk, which ensures it’ll stay there even after the modifications Facebook’s image uploader makes.
The author kindly provides a proof-of-concept image, which worked perfectly (the PHP shell obviously won’t execute, but it demonstrates that the data survived uploading).
Now, I could have submitted the bug there and then - we’ve got proof that images can be served with a content type of
text/html, and angle brackets aren’t encoded (which means we can certainly inject HTML).
But that’s boring, and everyone knows an XSS isn’t an XSS without an alert box.
The author also provides an XSS ready PNG, which I could just upload and be done. But since it references a remote JS file, I wasn’t too keen on the bug showing up in a referer log. Plus I wanted to try myself to create one of these images.
As mentioned in post, the first step is to craft a string, that when compressed using DEFLATE, produces the desired output. Which in this case is:
Rather than trying to create this by hand, I used a brute-force solution (I’m sure there are much better ways, but I wanted to whip up a script and leave it running):
- Convert the desired output to hex -
0xffto the string (one to two times)
0xffto the string (one to two times)
- Attempt to uncompress the string until an error isn’t thrown
- Check that the result contains our expected string
The script took a while to run, but it produced the following output:
Compressing the above confirms that we get our string back:
Combining the result, with the PHP code for reversing PNG filters and generating the image, gives us the following:
Which, when dumped, shows our payload:
We can then upload it to our advertiser library, and browse to it (with an extension of
Bypassing Link Shim
What can you do with an XSS on a CDN domain? Not a lot.
All I could come up with is a LinkShim bypass. LinkShim is script/tool which all external links on Facebook are forced through. This then checks for malicious content.
CDN URL’s however aren’t Link Shim’d, so we can use this as a bypass.
Moving from the Akamai CDN hostname to *.facebook.com
Redirects are pretty boring. So I thought I’d check to see if any
*.facebook.com DNS entries were pointing to the CDN.
photo.facebook.com (I forgot to screenshot the output of
dig before the patch, so here’s an entry from Google’s cache):
Any session cookies are marked as HTTPOnly, and we can’t make requests to
www.facebook.com. What do we do other than popping an alert box?
It’s possible for two pages from a different origin, but sharing the same parent domain, to interact with each other, providing they both set the
document.domain property to the parent domain.
www.facebook.com which does the same, and doesn’t have an
X-Frame-Options header set to
SAMEORIGIN (we’re still cross-origin at this point).
This wasn’t too difficult to find - Facebook has various plugins which are meant to be placed inside an
We can use the Page Plugin. It sets the
document.domain property, and also contains
fb_dtsg (the CSRF token Facebook uses).
What we now need to do is load the plugin inside an iframe, wait for the
onload event to fire, and extract the token from the content.
Notice how the alert box now shows
We now have access to the user’s CSRF token, which means we can make arbitrary requests on their behalf (such as posting a status, etc).
It’s also possible to issue XHR requests via the iframe to extract data from
www.facebook.com (rather than blindly post data with the token).
So it turns out an XSS on the CDN can do pretty much everything that one on the main site can.
Facebook quickly hot-fixed the issue by removing the forward DNS entry for
Whilst the content type issue still exists, it’s a lot less severe since the files are hosted on a sandboxed domain.
Bonus ASCII Art
One easter-egg I found was that if you append
.html to the URL (rather than replace the file extension), you get a cool ASCII art version of the image. This also works for images on Instagram (since they share the same CDN).