All posts

Drilling the redirect_uri in OAuth

I've been hunting for several years as a part-time hunter and have discovered many vulnerabilities. My most focused area, and my favorite, is the authentication class, which includes sign-up, sign-in, forgot password, 2FA, account deletion, etc. Nowadays, most websites have OAuth authentication beside normal credentials login, and many newcomers skip the OAuth.

It's reasonable since OAuth has a pre-defined standard implementation that ensures security by containing best practices. You may find many vulnerabilities in laboratory environments such as PortSwigger's. The main misconfigurations are

However, in the second case, the state parameter ensures that the flow is safe. Despite having an Open Redirect on a website, the state parameter does not allow an attacker to steal users' OAuth tokens. You may ask why? Since the state parameter value is bound to the user session, the attacker cannot trick the user to grab their token as well. Almost all OAuth authentications have a state parameter, so as a hunter, should we give up testing OAuth logins? The answer is NO :)

Attacking Scenarios

There are many attack vectors against OAuth logins depending on the setup. I'm not going to go through all of them, but I will discuss a case I've encountered several times, even in famous public HackerOne programs that I cannot name right now. Let me list three of them:

But wait a minute, the third one is not possible since redirect_uri checks are applied by the providers. Yes, you are right, so let's keep reading.

Different Flow

As mentioned, OAuth has a fairly standard and secure implementation. However, due to scalability or other factors that I do not even know, companies decide to alter the normal flow. This "out of normal" always needs double security checks, not only in OAuth but also in other technologies, such as login, 2FA, etc. The normal and simplified flow is something like this:

Normal OAuth flow — code exchanged for the auth token in one redirect

The authentication factor (Cookie or JWT) is issued immediately after the user is redirected back from the provider, using the code. Another pattern which I see, mostly in Application logins (not the web applications) is to extend the flow:

Extended OAuth flow with an in-between redirect before the auth token is issued

Here, as observed, there is an in-between phase before the authentication token is issued. An in-between redirect, honestly, I have no idea what it is for; maybe they have multiple channels for different platforms (Mobile, App, Web, etc.).

The Vulnerability

In the previous flow, if the state value is not properly checked and the second redirect URL is controllable by attackers, the OAuth system will be vulnerable, matching the third bullet point in the attack scenarios. Let me bring an example from one of my reports. During the hunting, I reached an OAuth in a MacOS application. The initial URL just after clicking on the "Login with Apple" button was:

https://www.redacted.com/lvpc_web/apple_auth
?login_id=95812a37-cb82-4789-bc73-dbe5f1079274

The response was a 301 redirect to the following URL:

https://appleid.apple.com/auth/authorize
?client_id=com.redacted.web
&redirect_uri=https%3A%2F%2Fredacted.com%2Flv%2Fv1%2Fcallback%2Fapple%2Flogin
&response_type=code%20id_token
&state=%7B%22platform%22%3A%22apple%22%2C%22id%22%3A%2295812a37-cb82-4789-bc73-dbe5f1079274%22%2C%22redirect_uri%22%3A%22https%3A%2F%2Fwww.redacted.com%2Flvpc_web%2Flogin_status%22%7D
&scope=name%20email
&response_mode=form_post
&frame_id=28f353fe-1c0e-4be6-b95a-3141f07b7f16
&m=11
&v=1.5.5

The redirect_uri in the query string is completely safe (the provider is Apple), and there is no attack here. Let's take a closer look at the state parameter:

{
  "platform": "apple",
  "id": "95812a37-cb82-4789-bc73-dbe5f1079274",
  "redirect_uri": "https://www.redacted.com/lvpc_web/login_status"
}

Here is the second redirect_uri, which is entirely outside the OAuth protocol. The parameter name could have been different, like rPath, and the value could also have been relative, such as /lvpc_web/login_status. Fortunately, my tests proved that the backend was not checking the state parameter. So I changed the redirect_uri to the illegitimate URL https://attacker.com and got a mismatch error. Here, I did some magic with @ and found out that https://[email protected]/ is acceptable, which allowed me to discover a one-click Account Takeover using a basic trick and some careful observation. If the URL were relative, I would definitely try to convert it into a full URL and add some tricks. As the state wasn't validated, I could craft a malicious link by setting the second redirect_uri parameter to my domain using the at-sign trick.

Final Words

Chasing the root cause, we will end up with "outside normal behavior" or "custom implementation". I discovered many vulnerabilities due to this root cause. In the example above, the programmer modified OAuth by adding an extra step. They used the state parameter to store data in a way it wasn't meant to be used, and on top of that, they didn't properly check the state value. So, if I want to end this post with some tips, they would be: