Using “Content-disposition: attachment” when streaming user-uploaded files is unfortunately incomplete protection against all cross-origin issues. Most savvy testers know that without it, a user could send a victim a link directly to a malicious uploaded file or <iframe> it in from their evil site, causing XSS & SSRF. When this header is sent down in response to a request, even if from an iframe, the browsers will force the user to download the file or cancel the request.
If they save and execute the file, they’re even more out of luck since locally privileged files are just as good as malware – but the forceful prompt is generally thought to better than the definite risk posed by not sending the header. Bottom line: the header is something you should send.
Developers seem to know this, too. I encountered this header for a second time in recent months, and in a way I’d guess was partially motivated by security. Given that I don’t think a lot of people know why it’s not good enough alone, I thought I’d write something up here.
HTTP is to firewalls as Java is to Browser Security
If you can upload arbitrary files to a site that can stream them back to you with a permalink, you can use Java to XSS other users of the site if you can trick them into visiting your site. If you remember from the GIFAR problem that Java’s same-origin policy, like Flash’s, is backwards, the issue will be easy to understand. If you don’t remember, here’s Java’s SOP in a nutshell: Java applets are only allowed to interact with the host where they were downloaded from, not the context where they’re being executed. This is the opposite of the browser’s normal active content SOP. If you <script src=http://X/foo.js>, the context of execution will be the current page, not origin X.
With that in mind, consider the crux of my message today: Java ignores the Content-Disposition header. This means that if you can upload a class file or jar file to a site and that file can be permalinked, then you have cross-origin applet capabilities. Here’s the timeline of an attack, which is similar to that of a Type I POST XSS:
0. Mallory creates an evil applet that connects back to its host and issues a few key HTTP requests to change a profile email address
1. Mallory uploads the evil applet to fileshare.com
2. Somewhere else on the web, Alice logs into the site, filesharing.com
3. Somehow, Mallory tricks Alice into visiting her site, evil.com
4. evil.com gives Alice HTML that contains an <applet> tag that points back to Mallory’s evil applet on fileshare.com:
<applet code=’EvilClass’ archive=’http://victim/users/myuser/evilfile.jar’>
5. The evil <applet> executes with Alice’s ambient authentication, and changes her email address to one that Mallory owns
6. Mallory resets the password on the account and Alice’s new password is sent back to Mallory
I couldn’t find much record of Java’s ignorance of the Content-Disposition header. I see kuza55 & stefano mention this fact somewhat quietly in what appears to be an excellent RIA slidedeck, but there’s no mention of it in Java documentation, the Browser Security Handbook, or any other reference I could find.
Keep in mind that, by the way, that Mallory doesn’t need to upload a file with a .class or .jar extension in order to include it remotely; she could also upload a file with an extension users are more likely to want, like .zip. Hell, you can probably store the serialized applet in any extension and invoke it with object=serializedApplet – I haven’t tested it and you shouldn’t be relying on extension checking for security, anyway.
The fixes, as always, are simple once you think about it in terms of what the application can do that the attacker can’t.
Fix #1: Require a POST to access the file. Although this is the least future-safe approach, it’s quick and it’s something you can make work in a WAF or in server configuration files. The attacker can’t force an <applet> tag to retrieve its content with a POST.
Fix #2: require a CSRF-quality token when downloading the file. This will require a forwarding action to front access to the files, but it’s a sturdier approach, long term.
Think before you save
Something else I’ve noticed – developers don’t think too creatively on defense when allowing users to control the names of their uploaded files. Aside from obvious null byte problems, developers are mostly smart enough to prevent files such as .php, .jsp, etc., from being stored, they typically don’t think about other types of files that
are also dangerous. Let’s imagine a site with the following structure for accessing user-provided files:
Here are 3 good questions to get you started thinking about alternate exploitation scenarios when testing file upload mechanisms:
1. Can users choose their own name during registration?
I’ll upload a file called ‘login.foo’ for my new user, ‘en-US’. The browser’s content sniffing will find the HTML in my file that has an unscary file extension and render it to my phishing victims the way I wanted. The Content-Disposition header stops this type of attack.
2. Have you considered files that have special meaning to web technologies?
I’ll upload my own application.cfc with whatever server-side code I want to execute (maybe an onRequestStart() request logger?)
If it’s not a ColdFusion app I could upload my own crossdomain.xml! Flash++!
If you don’t like Flash’s weird programming model you could just upload your own .htaccess, which allows you to do some cool shit – like force Apache to interpret my text file that I previously uploaded as a server-side CGI script with 2 lines:
AddHandler cgi-script txt
3. How good is that character validation of user-supplied file names, really?
Consider the old trick of ending a file name with :DATA in NT filesystems in order to get to the default data stream of a file without ending with the normal extension. Or maybe the application doesn’t canonicalize invalid Unicode, which you could abuse in countless ways – most notably because parameter.indexOf(“..”) == -1.
Hopefully you got something useful out of that. For anyone else that’s here, I’m already in Vegas getting ready to teach before the con starts – Twitter me if you’re going to be around and want to drink a beer or grab some sushi! P.S. grab a seat for Jeff’s talk on malicious code – scary one for the CSO’s.