If website uses OAuth multi-logins there is an easy way to log into somebody's account, protection is almost never implemented and people don't take into account that OAuth is also used for authentication.
OAuth2 is an authorization framework. Apparently it's very popular now. Disregards its popularity a lot of people don't understand it deeply enough to write proper and secure implementation.
OAuth1.a and OAuth2 are incompatible, some services use former(twitter, wtf, come on!), some latter, some of them have insufficient and poor documentation(in terms of security) etc. It took me a few hours to read OAuth2 draft thoroughly and I found a few interesting vectors. One of them I am exposing in this post.
It's really dangerous but very common vulnerability for multi-login OAuth websites.
A little bit of theory:
- response_type = code is server-side auth flow, should be used when possible, more secure than response_type = token. Provider returns 'code' with User's user-agent and Client sends along with client's credentials the code to obtain 'access_token'. Callback when user is redirected looks like site.com/oauth/callback?code=AQCOtAVov1Cu316rpqPfs-8nDb-jJEiF7aex9n05e2dq3oiXlDwubVoC8VEGNq10rSkyyFb3wKbtZh6xpgG59FsAMMSjIAr613Ly1usZ47jPqADzbDyVuotFaRiQux3g6Ut84nmAf9j-KEvsX0bEPH_aCekLNJ1QAnjpls0SL9ZSK-yw1wPQWQsBhbfMPNJ_LqI
- I remind you, OAuth is all about authorization, not authentication. What's the difference, you might ask. OAuth just gives to Client access to User's resources on Provider.
But very often Client authenticates you by 'profile_info' resource, thus we can call it authentication framework either.
- Choose Client which suits hack's "condition" - some site.com(we will use Pinterest as showcase) Start authentication process - click "Add OAuth Provider login". You need to get callback from Provider but should not visit it. It's quite difficult - all modern browsers redirect you automatically. I recommend bundle Firefox + NoRedirect extension.
- Do not visit the last URL(http://pinterest.com/connect/facebook/?code=AQCOtAVov1Cu316rpqPfs-8nDb-jJEiF7aex9n05e2dq3oiXlDwubVoC8VEGNq10rSkyyFb3wKbtZh6xpgG59FsAMMSjIAr613Ly1usZ47jPqADzbDyVuotFaRiQux3g6Ut84nmAf9j-KEvsX0bEPH_aCekLNJ1QAnjpls0SL9ZSK-yw1wPQWQsBhbfMPNJ_LqI#_=_), just save and put it into <img src="URL"> or <iframe> or anything else you prefer to send requests.
- Now all you need is to make the User(some certain target or random site.com user) to send HTTP request on your callback URL. You can force him to visit example.com/somepage.html which contains <iframe src=URL>, post <img> on his wall, send him an email/tweet, whatever. User must be logged in site.com when he sends the request.
Well done, your oauth account is attached to User's account on site.com.
- Voila, press Log In with that OAuth Provider - you are logged in directly to User's account on site.com.
Enjoy: read private messages, post comments, change payment details, have lulz, whatever. In fact account is yours now.
After you had enough fun you can just Disconnect that OAuth Provider and log out. Nobody will have an idea what has happened, you left no fingerprints!
How to detect, is certain OAuth implementation vulnerable?If site doesn't send 'state' param and redirect_uri param is static and doesn't contain any random hashes - it's vulnerable.
I know at least 10+ popular vulnerable sites: e.g. pinterest, digg, soundcloud, snip.it, bit.ly, stumbleupon etc. If you know more sites - please drop me a line at email@example.com
Also all Rails + Omniauth are vulnerable. Have fun(about 23,300 results)
- fixed for django https://github.com/omab/django-social-auth/issues/386
- fixed in omniauth https://github.com/intridea/omniauth/issues/612
- reported and fixed in soundcloud.com
- checking public libraries..
You are supposed to send special optional param 'state' - any random hash you get back by Provider in User's callback: ?code=123&state=HASH. Before adding OAuth account you MUST verify session[state] is equal params[state].
Classic: Insecure-by-default means insecure. Majority of developers don't use it at all - nobody pays for "optional" weird param :trollface:
MUST READ SECTION.
- $_SESSION['state'] == $_REQUEST['state'] is vulnerable code, was used a while ago in FB examples. Emtpy string equals empty string.
- I recommend you to filter 'code' in logs config.filter_parameters += [:code]
- state should not be equal form_authenticity_token(session[:csrf_token]) in rails
- if you implemented response_type=token flow w/o FB JS library, it's most likely vulnerable too.
At the moment I am working on "OAuth2 Security Proposal". The Proposal aims to make OAuth2 safer by changing its policies, rules and workflows. Most of the points are supposed to be backwards compatible but some of them are quite difficult to apply. Stay tuned, I am publishing it in a few days.