Hacker News new | past | comments | ask | show | jobs | submit login
An XSS on Facebook via PNGs and Wonky Content Types (fin1te.net)
309 points by renke1 on Mar 16, 2016 | hide | past | web | favorite | 45 comments

Man, I work as a frontend engineer at one of the big tech companies and I understood like 25% of this post.

And that's not meant to be a criticism, I'm just reflecting on my total ignorance of most security vulnerabilities. I know about and having implemented some measures against XSS / CSRF, but it's clear there are dozens of attack approaches I'm not even aware of.

I feel like I have some homework to do.

The root problem is that something which shouldn't be HTML was sent back with the HTML mime type.

Usually this happens with content type sniffing (IE no MIME type is specified) but it leaves the door open to attacks like these. It changes the handler of the input from code designed to care about security (IE your upload handler code) to code designed to care about usability (MIME sniffing heuristics, or in this case the decompression/rendering library).

When this happens you usually get bad/unexpected results, but it makes it easy to figure out where you can stop caring about implementation details- when the user input leaves the area of the code designed to secure it!

Well, he's a security specialist. And I feel he's figuring out new ways to attack. If you're not an attacker yourself, then I think you'll always lag behind.

I'm currently taking a security class and what I'm noticing is that I mistake the complicatedness for complexity but in essence most vulnerabilities and attacks have the same high level overview.

What I'm trying to say is: don't feel so bad, if you understood 25%, then you know more than you think. Perhaps you too are captured by the complicatedness of his post ;)

> I mistake the complicatedness for complexity

You conjugated the same word two ways. What do you mean?

Complexity is an adjective turned into a noun.

Complex -> complexity

On the other hand, complicatedness is a verb turned into an adjective turned into a noun.

Complicate -> complicated -> complicatedness

I read that as an implication that security concepts are not inherently difficult but are made difficult by people who explain it incorrectly.

I think he meant the difference between things being complicated (possibly unnecessarily) and complex (inherently). Simple things can be complicated to the level they're hard to understand, but it doesn't mean they are complex.

Yes this is what I meant. Perhaps I should've given a bit more context in my original post.

Here's a fun example. English has a lot of Latin and Germanic based words in it that you can construct entire sentences. I was surprised to find out as a non-native speaker. In my opinion, the Latin sentences seem way too complicated to express simple ideas.

Germanic example: In my anger I struck my small sword in his belly.

Latin example: In my rage I injected my gladius in his abdomen.

More examples: http://corrinejackson.com/wordpress/2013/04/23/tuesday-writi...


The two major issues here:

1. Allowing something that is not HTML (and user-supplied) to be returned by the server with the text/html MIME type, causing the browser to want to parse it as HTML.

2. A link between the CDN (Akamai) domains and a top-level sub-domain on Facebook (i.e. photos.facebook.com was aliased to some Akamai domains).

At this point the attacker is able to serve HTML from a Facebook domain. There are things that could be protected against here, but the attacker has a lot of vectors they can go for at this point.

It's interesting that you think working for a big tech company makes you especially qualified (or else you would not have mentioned it here). By working for a smaller company, you'd have to know these things or else face a world of pwnage.

> I feel like I have some homework to do.

Start with X-Frame-Options and HttpOnly cookies.

And Secure, if you're using HTTPS (which you should be)

Don't worry. Facebook's engineers missed this one too.

Main points for me:

1. Don't serve user supplied content from a subdomain of your main site. Facebook fixed this by removing the redirect from photo.facebook.com to their CDN, so now user content comes from *.akamaihd.net.

2. If a user uploads a PNG (or whatever), and you've validated it for that content type, don't serve it from your servers with a different content type. Clever users can make files that browsers will handle in different ways for different content types. Facebook's CDN still seems to allow serving files with the wrong (unvalidated) content type just by changing the file suffix in the URL.

> 1. Don't serve user supplied content from a subdomain of your main site. Facebook fixed this by removing the redirect from photo.facebook.com to their CDN, so now user content comes from *.akamaihd.net.

I don't see that as an issue. As long as you don't allow for wildcard extensions on your server it won't return a wrong content-type in this kind of situation. I wonder why it's even possible that you can change the .jpg to .html :| I'm pretty sure this is disabled by default in apache.

> don't serve it from your servers with a different content type.

so yeah, the problem of the content-type comes from the fact that you can manipulate the extension as you wish once uploaded.

> Facebook's CDN still seems to allow serving files with the wrong (unvalidated) content type just by changing the file suffix in the URL.

Is this common practice amongst websites? seems like a weird config to have enabled.

It is weird. I suppose as a result of storing file content by hash only (without extension), then determining the Content-Type header based on the user-supplied extension. Unless they're storing the original filename, they'll have to run through the binary data to determine file type.

Crucially though, is they removed the DNS forward to the CDN. Which means they're actually storing .html files on those CDN servers. For ads, presumably, but it seems terribly silly to be storing .html files there at all.

> If a user uploads a PNG (or whatever), and you've validated it for that content type, don't serve it from your servers with a different content type.

I can see changing the suffix for image types and converting the image on the fly - I don't know why that would be useful, but I can imagine someone doing that. But at the very least don't allow sending something you know to be an image with non-image headers.

Doesn't this XSS constitute a complete compromise of all Facebook user data, globally?

(Assuming it had been found by a "bad guy", instead of a white-hat researcher.)


* He could execute arbitrary code in the browser of any user that views his picture

* Including uploading all private photos and chats of that user to somewhere else

* Or changing their password (assuming 2FA is not used)

* He could have propagated this exploit to all Facebook users by simply uploading his XSS picture into his victim's profile, and from there into second-degree victims, and so on? According to the "6 degrees of separation" theory, this would have been very fast

Or am I missing something?

He couldn't have just "uploaded" images to a page, since facebook would output the original path (B1GL0N6H45H.png), where this relies on the .html extension. Easy enough to link people through Messenger or another site to that page, though. The core vector is the fact it's served off the photos.facebook.com domain.

A Facebook worm!

Excellent writeup and congratulations to Jack (fin1te)! He's been a long time bug bounty hunter for Facebook and he's joining their Product Security team in three weeks.

Hmm, I wonder if there is anything interesting you could do by creating a Service Worker for the CDN domain. It seems that the CDN will serve arbitrary files with a text/javascript Content-Type, which is the requirement to set one up. Once created, AFAIK, such a worker remains attached to the domain until an occasional attempt by the browser to refresh it fails, and it can intercept and modify any requests from webpages on its origin, as well as navigation there. But it doesn't affect requests to that origin from other origins, like the typical <img src> from facebook.com, so it won't be able to do much.

It seems that you click "download" on Facebook images, you'll get redirected to a CDN URL. So there's that. You could see what images the user is trying to download and edit them in flight... not a big deal, but it's something.

ServiceWorkers are scoped to the basename of the path they are served from, so you could only intercept a small subset of all possible CDN URLs. In the example, this would be resources under `/hads-ak-xat1/t45.1600-2/`. But you probably can't create a ServiceWorker here anyways as you need a route that returns valid Javascript of your choosing with a Content-Type `text/javascript`. Usually when I've seen an opportunity for ServiceWorker exploits it's due to a JSONP endpoint that does not sanitize its callback parameter.

If you could create a valid ServiceWorker at this route, and load it inside some same-domain HTML page, then you could theoretically use it to intercept and rewrite responses to any resources on the domain under its path. Depending on how you rewrite the response you might be able to get the browser to cache the rewritten resource, which could then get used by other domains. Theoretically. I have never tested this :)

The default scope is the basename, but you can customize it, can't you?

As I said, the text/javascript Content-Type is another one the CDN is willing to serve if you change the extension. I'm silly and didn't think of the fact that unlike HTML, a JS file can't be random binary garbage with a payload embedded somewhere in the middle. However, I am not sure that some allowed image format doesn't allow putting, say, // close enough to the beginning of the file that it will work. It seems like it might be possible with the JPEG header, but of course it depends on the post processing done. (Or maybe there is some way to upload non-images?)

I seem to remember trying this (passing {scope:'/'} in the register call), and it doesn't work. Some googling seems to agree:

"Service Workers are restricted by the path of the Service Worker script unless the Service-Worker-Scope: header is set" [1]

I do wish the spec required a 'Content-type: text/service-worker', as that would effectively eliminate accidental ServiceWorkers as a threat.

[1] https://infrequently.org/2014/12/psa-service-workers-are-com...

Hmm; I don't see that in the current spec [1], and that link is pretty old. I should test it.


The CDN to *.facebook.com find is really cool. May I ask what the bounty reward was for you?

That is incredibly clever, particularly exploiting DEFLATE to generate text. I would've never thought of that.

They didn't use DEFLATE to generate the text. They used a known text and prepended garbage to it until it DEFLATEd correctly (to any result). Small difference, but still.

The author seems aware that it is possible, however:

> 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):

He just didn't want to learn DEFLATE right then, right there. He got the same result.

FWIW, I don't think this is actually hard. DEFLATE has a small header that tells you want type of compressed data follows; one of the types is "literally, this data" which is up to 64KiB long. (i.e., a plenty long enough string to get the job done.)

That ascii art is cool!! Is it a feature of the Akamai CDN?

Wow. I hope he alerted facebook before posting the blog entry.

From TFA


Facebook quickly hot-fixed the issue by removing the forward DNS entry for photo.facebook.com.


Pretty clever, but I'm wondering how this would be exploited. To insert an XSS attack on facebook.com, he would have to be able to change the extension in url in the facebook page of an uploaded image to .html, right? Don't see a way of doing that.

Am I missing something, or the attack assumes you can convince a victim to go to the given URL through other means? Like spreading through IM, E-mail...

There's multiple way to spread it, you put that url inside an iframe and then you could upload it on some ad network, you could build some viral content somewhere and let it spread that way. It's pretty easy to spread an XSS.

After reading the comments here, it seems like the attack would indeed assume you spread the alternative URL with the .html extension through other means like IM, ...

Wow. Facebook had managed to fail on a thing absent in much less popular sites since the day one.

A CDN absolutely mustn't allow a user to browse to a file with a different extension than it was uploaded with. It must return 404 in this case. II think the flaw is much more severe than has been discovered.

and they say CSP is useless...

I don't think CSP could have prevented this? Facebook.com is already CSP enabled, but CSP only prevents cross-origin injection of certain resource types (images, scripts, Javascript). This attack originated from Facebooks own domain and was served (well, sniffed) as HTML.

Anyway, CSP clearly isn't useless... but try deploying it on your average Wordpress blog sometime.

> CSP only prevents cross-origin injection of certain resource types (images, scripts, Javascript).

Are you sure ONLY? Enable CSP on a XSS-vulnerable website, send a payload, CSP prevents XSS from executing if your policy say so.

> Anyway, CSP clearly isn't useless... but try deploying it on your average Wordpress blog sometime.

What issues have you found with this?

Not OP, but my guess is random plugins stop working with strict (i.e. secure) policies, if they weren't written with CSP in mind.

Its not really plugins. The vast majority of wordpress themes out there come with inline css, inline javascript, and prebaked dependencies on things like jquery and google fonts hosted on 3rd party CDNs.

Bad for development time but good for security then I'd guess.

That reminded me the fun I had at uni (some time ago..) trying to exploit a buffer overflow vulnerability of an ftp server... for the security class...

Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact