Wednesday, August 1, 2012

SaferWeb: OAuth2.a or Let's Just Fix It

Eran and others, chill out. We should stop whining to nobody. I prefer rather "we gotta fix this, this and discuss that" than "it is bad"-attitude. Some people were surprised because they like OAuth2.

What about me? I was not surprised because OAuth2 is far from perfect. But there is no reason to give up, it's in our hands. ( By the way I'm waiting for comments on Hacker News OK?)

Below I explain some security and usability concerns about current OAuth2 and propose(I do, not just say 'it is bad') improvements to make it more agile and safe-by-default. OAuth2.a is going to be easier to implement and more secure.

TL;DR:

  1. redirect_uri can be on any domain and amount of redirect_uri-s is unlimited. They are whitelisted and only exact match verification is applied(redirect_uri IS NOT flexible domain.com/*). Client sets redirect_uri-s on his admin page in Provider.
  2. for every redirect_uri MUST be defined certain response_type - token or code. It must not be possible to set response_type in authorize URL. Every redirect_uri has its own defined response_type
  3. Most Common OAuth2 CSRF Vulnerability from my previous post. We should either introduce a new *compulsory* param(e.g. csrf_token) or just raise awareness about the issue.
  4. We need either assign 'scope' to certain redirect_uri as well as response_type(it MUST not be in URL) or allow user to choose what parts of scope to allow. It's up to Provider's implementation.
  5. We should introduce 'mass refreshing' of access_token-s. Client sends an array of refresh_token-s and client's credentials and get's hash with refresh_token=>access token.
  6. We need to define some DEFAULT URL paths and error codes to add a little bit more "interoperability". Really, is it so damn hard to keep your endpoints and error codes similar to other services?

Points and details:

1. Matching redirect_uri domain is futile! We SHOULD NOT have only 1 domain allowed.
Well, I have no idea why all providers require only 1 domain in redirect_uri. Actually, stealing 'code' is worth nothing. 

Providers MUST require 'redirect_uri' to obtain access_token(according specs). If 'code' was issued for different redirect_uri - server side will not get access_token for your stolen code. Again - even if I steal 'code' with redirect_uri = mydomain.com anyway I won't be able to use it. 
Providers, relax, all redirect_uri checks are futile, if you follow OAuth documentation(ugly example - social network vk.com doesn't https://gist.github.com/3075898 and if you steal code you can get access_token for it)
   In order to prevent such an attack, the authorization server MUST ensure that the redirection URI used to obtain the authorization code is identical to the redirection URI provided when exchanging the authorization code for an access token.  The authorization server MUST require public clients and SHOULD require confidential clients to register their redirection URIs.  If a redirection URI is provided in the request, the authorization server MUST validate it against the registered value.

Provider SHOULD allow many redirect_uri-s for one Client. Provider SHOULD NOT restrict all redirect_uri-s to match the same domain.

Now you are able to add a lot of redirect_uris on various domains(e.g. development mode and multi lang mode):
site.com/facebook/callback
site.de/facebook/callback
site.localdev/facebook/callback 

redirect_uri checks MUST be done in exact match mode against whitelist of callbacks. (Currently it only checks domains site.com/*anything*). It will mitigate all kinds of XSS-leaks, backend-leaks, referer leaks etc once and for all. Also, please remember, callback MUST redirect user instantly.

Provider SHOULD NOT require redirect_uri param anymore to obtain access_token. All redirect_uri-s are whitelisted now, redirect_uri forgery will not work anymore. It will really simplify the process since you don't need to remember current callback URL to obtain access_token.


2. Currently Authorization Code Flow is not any safer than Implicit Flow
Implicit Flow is the most insecure OAuth flow because access_token is exposed to user-agent, scripts(internal/external), possible XSSes and so on - a lot of risks and ways to compromise it.
If hacker get's your access_token he automatically gains all the permissions (described in 'scope') on your account in OAuth Provider(google, twitter, facebook, github etc).  

Avoid it!(quote from IEFT):
However, this convenience should be weighed against. the security implications of using implicit grants, especially when the authorization code grant type is available.
Even if website uses response_type=code, attacker easily replaces it with response_type=token in authorize URL, points redirect_uri to some XSS-exploitable page on your domain, extracts document.location.hash when user is back. He receives User's access_token, jack pot. Stolen access_token means hacker gets (limited) access to your resources on provider.

Even if website uses Authorization Code Flow you can 'downgrade' it to response_type=token and obtain opaque access_token. This is a gaping flaw in OAuth2.


response_type param MUST be defined along with redirect_uri in admin panel of client. Provider MUST NOT accept change of response_type within authorization process. If site uses response_type=code it must be impossible to use response_type=token until you change Client's details in Provider's admin page.



3. Scope or I still need my children alive.
Here is a nice article on this topic by Zach Holman, written ~2 years ago: http://zachholman.com/2011/01/oauth_will_murder_your_children/
Nothing has changed.

Let me remind you that Client sets 'scope' in authorize URL param. What does it mean? There is absolutely no guarantee that User gave you those scopes you asked for!

Actually, when I log in with facebook/twitter somewhere I just replace 'scope' with empty string and Client simply gets access to some profile details and my user_id. MWAHAHA.

There are two ways to fix it:
first - bind scope value to certain registered redirect_uri. If User authorized this 'redirect_uri' - than Client definitely will get access to scope he asked for. No damn surprises.
second - stop requiring stupid permissions. It's up to me what to give you. Providers should let me choose as in Holman's article - what to allow and what to deny. It's MY resources, it's MY permission.


4. CSRF or Stop Insecure-By-Default OK?
Yes, it is rather Client's vulnerability than in Provider. But it is still vulnerability because it's really common and OAuth2 should fix it.
Again, two ways to go:
first - add new compulsory param 'csrf_token' specially for CSRF prevention.
second - educate developers about CSRF and make documentation remarks cleaner. I see mention about CSRF in Providers' manuals very rarely. Actually 'state' was not supposed to be CSRF token but just to keep state of your application. You can store both values using delimiter: state=CSRFTOKEN_APPSTATE and I think it's OK.

This is it. 

I don't propose to add encryption. No fucking way. It was extremely tedious to work with OAuth1 and I am fully OK with HTTPS.
I also don't propose oauth to be more interoperable about token generation and length and other stuff. It would be just utopia, it's not our business how Providers implement OAuth FRAMEWORK.
I just want you guys to fix these few minor things(many redirect_uri-s, scope is not reliable etc)  and 2 major vulnerabilities(response_type replacement and common CSRF).

1 comment:

  1. Do you have any suggestions on how to fix "response_type replacement" ?

    ReplyDelete