XSS Vulnerability in Internet Explorer HTML Attachment Download

Update: MS fixed this issue in the IE8 6/9/09 security update.  Now IE8 behaves like Firefox (unclear on whether ‘X-Download-Options: noopen’ still exists at all).

I have noticed a Cross-Site Scripting vulnerability in the way Internet Explorer handles the downloading and opening of HTML files when they are downloaded as an attachment, rather than opened normally. This vulnerability exists in all versions of Internet Explorer, including the latest patch level of IE7 as of 12/23/08.

This vulnerability related to sites serving user-submitted HTML files with “Content-Disposition: attachment”.

When directly opening a downloaded HTML file, Internet Explorer violates the Same Origin Policy by allowing any script inside the downloaded file to access the cookies of the site the file was downloaded from. This script should be restricted to running in a local context, not a domain context.

Firefox exhibits better behavior by first downloading the HTML attachment and then opening it with a file:// URL. When scripts in the downloaded HTML file are executed, they are treated as if run from a local file, not as if run from the domain the file was downloaded from, and they cannot access the source domain’s cookies.

This vulnerability would allow an attacker to execute a Cross-Site Scripting attack on a site that allowed uploading file attachments. An HTML file could be uploaded containing malicious script that could steal user credentials or forge user actions on the site (if downloaded and opened by an IE user).

I have created a screencast reproducing the incorrect behavior in Internet Explorer as well as the correct behavior in Firefox. Additionally, I’ve set up a downloadable HTML file that you can use to reproduce the issue yourself!

The screencast and example are available at: https://www.awgh.org/iebug

Microsoft has addressed this issue in IE8, but their solution leaves me with some questions.  The write-up in their development blog is here, skip down to the section titled “MIME-Handling: Force Save”.  This section acknowledges that this is a potential vector for script injection in IE and describes the solution as implemented in IE8.

The solution in IE8:

The web server can set the response header “X-Download-Options” to the value “noopen”.  This will tell Internet Explorer to only offer the option to save the file or cancel.  It simply removes the “Open” option when this option is set.

I see two problems with this solution.  First, call me a pessimist, but I can see someone actually disabling “noopen” simply to bring back the “Open” dialog option.

Second, I doubt that most web server admins are going to be worried enough about this issue to remember to set this header, or even know they should do it.  It’s a bit of an esoteric bug – it only affects sites that serve untrusted HTML with “Content-Disposition: attachment”.  Even if Microsoft web servers set “noopen” by default, I doubt that most LAMP admins will bother adding this to their server options.

Why force the server to fix this problem?  Why not treat the “Open” option the same way Firefox does, by first downloading the file, then opening it with only a local script context?

This method of script injection will continue to work in Internet Explorer 8, as long as the site has not set the “X-Download-Options” header to “noopen”.

So the moral of the story:  If you are the admin of a site that serves untrusted HTML files with “Content-Disposition: attachment” set, please make sure the “X-Download-Options” header is set to “noopen”.

Weaponizing Mailinator

There has always been something deeply unsettling to me about the ‘Forgot Password’ functionality on many web sites.

The ‘Forgot Password’ page exists solely to help unauthenticated users bypass the usual means of authentication.

For whatever reason, many developers overlook the importance of locking this down, even after the issue of too-easily-guessable questions in Yahoo’s ‘Forgot Password’ procedure got a lot of media attention during the US presidential campaign after Gov. Palin’s webmail was hacked.

Even if the questions were based on specific preferences and more difficult to guess, very few sites will check for brute-force attempts on the ‘Forgot Password’ page, even though protections against brute-forcing have often been implemented in the more prestigious login page.

One other recommendation I usually make is the banning of email addresses from Mailinator, Slopsbox, and similar anonymous email services in registration.

If you’re not familiar with Mailinator, it’s an email server which displays ALL received emails to anyone who visits their web site.  Say you were registering for some web site and they asked for an address to send the validation email to.  You can just enter any email address at Mailinator, for example asdf@mailinator.com, and then go to www.mailinator.com and read the response.  This is great for not having to give out your real email.

What this means, however, is that I can simply go to the ‘Forgot Password’ page, which usually requires only an email address, enter asdf@mailinator.com, and a password reset email will be sent to Mailinator where I can collect it anonymously.  Any user account on any web service which was registered to a Mailinator email address can be compromised simply by guessing the email address.

Here’s where the brute-forcing comes in.  Since most sites let you make as many guesses on the ‘Forgot Password’ page as you’d like, there is nothing stopping an attacker from simply guessing email addresses at full tilt.

To demonstrate the effectiveness of this technique, I’ve written two example scripts, called the Mailinator-nator, which are available here.

The first script is called forgot-pwd-force.py, this script does the following:
1) Brute forces “Forgot Password” forms that only require email addresses on a hardcoded list of sites, using a wordlist of usernames.
2) For each username, tries each of the Mailinator domain aliases (Mailinator has a number of different domain names that point to the same place).

The second script is called mailinator-scan.py, this script does the following:
1) Reads a wordlist of usernames from a file.
2) For each username, connects to Mailinator and logs all emails to that user which contain the word “password”.

To use these two together, first add your target sites to forgot-pwd-force.py.  You can use one of my included wordlists or make your own, just be sure to use the same wordlist for both scripts.

Next, run the first script to force the target site to generate password reset emails to Mailinator addresses.

Wait a few minutes, and then run the second script to collect all of the return emails from the Mailinator server.

The second script can also be run as a cron job, which lets you troll Mailinator for password reset emails that you did not trigger yourself!  Mailinator deletes all received emails within an hour or two, so you may have to tinker with it to find a good interval.

I love Mailinator, so I checked and this doesn’t seem to violate their terms of service.  Looking at their site, they don’t seem to have terms of service!  This makes some kind of sense, since all users to the site are anonymous.  That said, actually logging in to a web site with a password recovered in this way is probably illegal in most jurisdictions so don’t do it.

As a site developer, what can you do to prevent these kinds of problems?

1) Ban registration emails to Mailinator and all of its domain aliases.

2) After 10 or so failed attempts to guess an answer on the ‘Forgot Password’ page, ban the IP for 5-15 minutes.

3) Require more than just the email address to send a password reset email.  Consider at least two factors: email address AND one security question.

These three measures will protect your ‘Forgot Password’ page from brute-forcing and dictionary attacks, as well as protecting your users from having their accounts stolen.

As a user of Mailinator, you can reduce your exposure to this risk by making use of the ‘Delete This Email’ feature of Mailinator and by using a long, difficult to guess user name.