Tuesday, August 28, 2012

OAuth2: One access_token To Rule Them All




One access_token to rule them all...
I demonstrated The Most Common OAuth2 Vulnerability in Authorization Code Flow a while ago – signing in the Victim's account(followed by getting an access to Victim's resources on that website) by connecting Attacker's oauth account through CSRF-callback URL with Attacker's code. Despite popularity of that attack, it can be easily mitigated with 'state' param(to prevent CSRF) – and now it's fixed in popular ruby/python libraries.

Now I want to share a very straightforward attack for Implicit Flow based websites(realized in brainstorming with @isciurus). There is no proper mitigation so far.

Generally speaking:
  1. 'access_token' is a string that identifies your Resources on Provider. No other parameters are required to call Provider's API. There is no guarantee user's access_token1 for client1 is not used by client2 or client75 - nothing stops them.
  2. Let's assume I create a website: e.g. superfunnypicturez.com. I ask my users to authorize my Client on facebook and I don't ask any scopes at all - I need only "/me" endpoint with "uid" param to be available. They authorize my Client because superfunnypicturez.com requires only 'read' access and nothing seems to be dangerous. I get their 'access_token's and now I can request /me endpoint and get their "uid"(often used for authentication)
  3. Let's assume there is another site: e.g. weuseimplicitflow.com and yep, it uses Implicit Flow(receives token via CALLBACK#access_token=123qwe...). It's a pity - this website authenticates users by given access_token. Most likely it sends access_token on server-side and invokes /me endpoint from there.
  4. Since I am admin of superfunnypicturez.com and you authorized my Client and gave me an access_token from your account I just put that access token in callback URL: CALLBACK#access_token=YOUR_TOKEN and now weuseimplicitflow.com's Client authenticates me as you because that token I just provided returns your 'uid' when /me endpoint is called. I need only one access_token from your Provider's account to rule all your accounts on 3rd party websites that use Implicit Flow.
Recap:
OAuth2 is extremely insecure for authentication goals by default. 
  • Auth Code Flow: You must use 'state' parameter and verify - is this user the same user you sent to authorization URL by checking state value from session and returned one.
  • Implicit Flow: You must ensure that access_token you are going to use is issued for your Client. Please, use this URL for Facebook: https://graph.facebook.com/app?fields=id&access_token=TOKEN. Not all providers support this feature though. If access_token is anyhow obtained from User (not from Provider) - you must verify is this access_token issued for your Client.
Oh, also:
As I mentioned in my previous post on stupidity of OAuth2 - if Provider has Implicit Flow as an option - lots of Users can be compromised someday. If a single XSS is found on Client's domain - hackers can steal all access_tokens with it even if your website uses Auth Code Flow by just changing response_type param in authorize URL. 


OAuth2.a - simple and secure. Join! Honestly, yeah, OAuth2 is Road To Hell. Deal with it. But OAuth2.a is road to Simple and Secure both Authentication and Authorization. I am implementing Provider - charm. If you trust me and have a popular RoR website that needs OAuth - ping me, I will help you to introduce OAuth2.a and make your API users happier.
Author of this article is awesome - you can actually hire him.

10 comments:

  1. "There is no proper mitigation so far" - there is, but beyond the standard of course. Facebook uses signed_request, where signature verification is ok iff access_token/code issued for the correct client.
    My shampagne is almost over, cya.

    ReplyDelete
    Replies
    1. Author of this comment is no less awesome, you can hire him as well.

      Delete
  2. I pointed out this attack back in January http://www.thread-safe.com/2012/01/problem-with-oauth-for-authentication.html. That resulted in Facebook and others making changes to the way they were mis-using OAuth for authentication. http://www.thread-safe.com/2012/02/more-on-oauth-implicit-flow-application.html

    Egor, you are correct that OAuth is a Authorization protocol that you can use to create a Authentication protocol but, there needs to be additional security considerations.

    OpenID Connect addresses this by providing a additional authentication token intended for the client and not the RS. That token has the client_id of the intended recipient and a hash of the access token in the implicit flow. This is signed by the Authorization server to prevent tampering.

    OAuth itself will not address this, other than with a general warning as Authentication is out of scope for the specification.

    Facebook and openID Connect use a similar signed token mechanism though Facebook's assumes a single identity provider so needed to be extended for Connect, and connect includes a hash of the access token rather than the whole access token in the signed object to give more flexibility.

    Caution needs to be applied to people who are not experts trying to roll there own Authentication protocols.

    Regards
    John Bradley

    ReplyDelete
    Replies
    1. I found your blog and those posts a few weeks ago. It's a very good read, exactly what I am interested in and our points are almost similar.

      But I don't see profit in signed_request thing btw. It is just signed hash with user_id code - it's client-sided Auth Code Flow. Why so, if eventually you need to provide signed_request to backend and verify signature and obtain a token with code. Better to just use normal Auth Code Flow and redirects.

      Anyway, signed_request helps against both Auth Code and Implicit attacks:
      1. you cannot put your token in database, you can only provide code. and code of another person is useless if it's not issued for this certain app.
      2. you also cannot use CSRF - signed_request is sent with postMessage.

      Finally, my point is OAuth2 is OK for authentication(yes, keep using) only if you follow rules described above:

      Auth Code Flow: You must use 'state' parameter and verify - is this user the same user you sent to authorization URL by checking state value from session and returned one.
      Implicit Flow: You must ensure that access_token you are going to use is issued for your Client. Please, use this URL for Facebook: https://graph.facebook.com/app?fields=id&access_token=TOKEN. Not all providers support this feature though. If access_token is anyhow obtained from User (not from Provider) - you must verify is this access_token issued for your Client.

      Delete
  3. Your point 1 was not always true.

    Code could also be compromised by swapping it in a similar way with public clients.

    We made a change to OAuth in Draft 30 that requires the client to send it's client_id to the token endpoint so the Authorization server can check that it is giving the access token to the client that originally asked for it. For that to be effective the server needs to check the client_id as is now required.

    A lot of the native apps were using the flow without authenticating to the token endpoint and were vulnerable.

    The openID Connect id_token is asymmetrically signed so a JS client can verify the signature without compromising a symmetric secret. I agree that Facebook's signed request needs to be passed back to a host for verification, so is not a great advantage over the code flow.

    One reason Facebook had for wanting a id token in openID Connect was a belief that getting the user_id quickly was an important feature for RP. Getting the ID in the front channel and customizing the UI while the server makes the back channel calls to get the rest of the claims was one of there must haves.

    One confusing thing about Facebook's signed request is that it contains code and not a access token. There documentation is wrong, causing developers not to use it.

    Certainly using state to protect against XSRF is a MUST in openID Connect but only RECOMMENDED in OAUTH. We also added a nonce value that the client can send that is returned in the signed id_token that allows a tighter binding of the session to the token. One problem with state is that it is not signed and can be tampered with by an attacker, so care needs to be taken with it. That however is not a issue for the XSRF attack.

    Authentication can be a tricky thing. Without doing a complete security review I would not use OAuth 2 on it's own for Authentication. You just need to look at the mistakes Facebook as made trying to do that on there own.

    Regards
    John B.


    ReplyDelete
    Replies
    1. >Code could also be compromised by swapping it in a similar way with public clients.

      it sounds like real hacking with putting virus on victims machine

      >We made a change to OAuth in Draft 30 that requires the client to send it's client_id to the token endpoint so the Authorization server can check that it is giving the access token to the client that originally asked for it. For that to be effective the server needs to check the client_id as is now required.

      you mean before users called ?access_token=123 now they have to ?access_token=123&client_id=321 ?
      My first idea was to propose the same BUT. isnt' it easier just to ask "check is it issued for your client" since developers have to change their codes anyway

      >A lot of the native apps were using the flow without authenticating to the token endpoint and were vulnerable.

      I would rather backends of these apps were vulnerable. It's server side's business to do authentication stuff and it was relying on access token only..

      > One reason Facebook had for wanting a id token in openID Connect was a belief that getting the user_id quickly was an important feature for RP.

      anybody, tell facebook that oauth2 is framework and it's up to them what to do with it :)) They can also pass user_id back with #access_token=123&user_id=123 and same in Auth Code Flow.

      >One confusing thing about Facebook's signed request is that it contains code and not a access token. There documentation is wrong, causing developers not to use it.

      exactly - now it looks like futile round trips - you just obtained a signed hash and you can trust it but it has only code and you again need to exchange it.

      Question, what do you think about my simplification:
      1. in auth code flow application gets Token, not code. But this token is already expired so they just need to refresh it. One code for 2 goals - for refreshing and for obtaining first time.
      2. to refresh token you have to provide client creds and Old token. No refresh token needed. Old token is stored in database and we can identify what do you need.
      I think now it's a bit easier to manage with expired tokens.

      Delete
  4. Code is intended to be small enough that a user can type it in from an out of band source on a device. That is also why it is single use.

    For the id_token the hash is of the access token that is issued with the id_token in the implicit flow. You don't need an extra round trip to verify it.

    No hacking of the client was required to compromise the code if the client was not authenticating itself to the token endpoint. Only getting the user to give you a code in the first place then replaying it to the client being attacked. It is the same attack as the implicit flow but with swapping code.

    ReplyDelete
    Replies
    1. >Code is intended to be small enough that a user can type it in from an out of band source on a device. That is also why it is single use.

      Like this one? Seems not so small. Anyway first time token can be also very small and expired. 'code' as entitiy is not useful.

      b1a5cc3ba0cf1c55558c7909-581207612%7Ct1GCu9z-4tlUypFIxULjprmGlr4

      did you know that facebook does NOT expire codes? here is how i can use code again if i find some xss on some client:


      iframe src="https://www.facebook.com/dialog/permissions.request?app_id=159618457465836&display=page&next=http://magru.net/users/auth/facebook/callback&response_type=code" name=z onload="console.log(z.document.referrer)"

      hole

      Delete
  5. I want to say that OAuth is for authorization . Access_Token is just like password. Assume you authorized a app that built by a hacker. and the hacker use the access_Token accessing your account on other 3rd part apps. is this just like phishing ?

    ReplyDelete
  6. @coco, you're right, but it's not like password. password is high level thing, access token is provider specific. but looks similar

    ReplyDelete