How Secure Is Your OAuth? Insights from 100 Websites

February 18, 2025 Eviatar Gerzi

Oauth Cover Image

You might not recognize the term “OAuth,” otherwise known as Open Authorization, but chances are you’ve used it without even realizing it. Every time you log into an app or website using Google, Facebook or another account, OAuth grants that service limited access to your data without sharing your password. OAuth simplifies user authentication across platforms, making it a key part of secure online experiences. Yet, behind this simplicity lies a complex system that, if not implemented correctly, can lead to account takeover.

In this blog post, we will explore our analysis of OAuth implementations across 100 websites, highlighting common security issues and discussing the techniques used to uncover them. As part of this effort, we developed an open-source tool called “oauth-hunter” to assist in identifying vulnerabilities and streamline our research process.

tl;dr

  • We covered 100 websites using OAuth, ranging from popular large platforms to smaller sites.
  • 28 websites didn’t verify the redirect_uri parameter properly, and 18 of those 28 allow adding any path after the domain.
  • 21 websites didn’t verify the state parameter properly, and 8 of those 21 allow the attacker to trick the victim into completing the OAuth flow, thereby granting access to the attacker’s session.
  • 7 websites had pre-account takeover vulnerabilities.

The Research

The research was driven by the need to explore how websites implement OAuth and whether they follow best practices to prevent account takeovers. Our goal was to assess how effectively various websites safeguard against OAuth-related vulnerabilities, focusing on the security of their authentication processes.

During our investigation, we analyzed 100 websites, ranging from large and popular to small websites. The majority used common identity providers (IdPs):

  • Facebook (47%)
  • GitHub (35%)
  • GitLab (1%)
  • Others (17%)

Before diving into the results, let’s first familiarize ourselves with OAuth basics.

How OAuth Works

The Authorization Code Flow in the diagram below (Figure 1) illustrates the OAuth process. This flow is one of the most common and secure methods for granting access, as it involves exchanging a temporary code for an access token, ensuring sensitive data like credentials aren’t exposed to the client.

Before starting, let’s review four key components of the OAuth flow that we will demonstrate through the diagram below:

1. Resource owner: The user who owns the data or resource, e.g., “John” in our case.

2. Client: The application or website, such as example.com in our case, that requests access to the resource (e.g., John’s profile information or data) on behalf of the user (John). This is also the website that John is trying to connect to.

3. Authorization server (also known as OAuth provider): The service that authenticates the user and grants access tokens, e.g., GitHub.

4. Resource server: The server that hosts the protected resources (data or services) that the client wants to access on behalf of the resource owner. In our case, the resource server and the authorization server are the same entity — GitHub. Therefore, we will not be addressing the resource server separately in this blog.

Code flow diagram

 

Figure 1 – OAuth Authorization Code Flow

Let’s go over the authorization code flow (Figure 1):

1. John, the resource owner, visits the website example.com (OAuth Client) and initiates the login process by selecting the GitHub option.

2. Since example.com doesn’t know John, it requests proof of his identity.

3. John is redirected to GitHub, the authorization server, which asks for John’s permission to authorize example.com to access his GitHub data.

4. GitHub sends John a unique authorization code to share with example.com.

5. John shares the authorization code with example.com.

6. Example.com then forwards John’s authorization code to GitHub and requests an access token.

7. After verifying the authorization code, GitHub sends the access token to example.com.

8. Example.com uses the access token to request user information (such as John’s identity) from GitHub.

9. GitHub validates the access token and returns the user’s identity (e.g., “John”).

10. Example.com authenticates John based on the received identity and grants him access to his resources.

Other flows may vary depending on specific OAuth parameters in different implementations.

Understanding Key OAuth Parameters

Now that we’ve walked through the basic OAuth Authorization Code flow, let’s take a look at some OAuth parameters to help understand the attack vectors:

  • redirect_uri: The URI where the OAuth provider will redirect the user (in our case, John) after they have either granted or denied authorization. This URI must be pre-registered with the OAuth provider as part of the client application’s registration process.
  • response_type: Specifies what kind of response the client application expects from the OAuth provider. The most common response_type values are:
    • code:
      • Flow Name: Authorization Code grant.
      • Description: The client expects the resource owner (user) to authorize the request, which prompts the authorization server to issue an authorization code. This code can then be exchanged by the client for an access token, allowing the client to access protected resources on behalf of the resource owner. This is the most common type used in web server flows.
    • token:
      • Flow Name: Implicit Grant.
      • Description: The client expects an access token directly from the resource owner. This is often used in client-side applications, like single-page apps (SPAs) such as Gmail or Facebook, where the client doesn’t have a backend server to handle the exchange of an authorization code.
    • client_id: A unique identifier that the OAuth provider (in our case, GitHub) issues to the client application (example.com).
    • scope: Allows the client to request specific permissions when it initiates the OAuth flow.
    • state: A security feature used to prevent cross-site request forgery (CSRF) attacks.
    • prompt: A parameter that controls how the authorization server prompts the user during the authentication process. Common options include:
      • none: No user interaction; will fail if user consent or authentication is required.
      • login: Forces the user to log in again, regardless of their current session.
      • consent: Forces the user to consent to the requested permissions, even if consent was previously granted.
      • select_account: Prompts the user to select from multiple accounts if they are logged in with more than one.
    • response_mode: Specifies how the authorization response is returned to the client. Common options include:
      • query: The response is sent as query parameters (“?”) in the URI.
      • fragment: The response is sent as fragment (“#”) parameters in the URI.
      • form_post: The response is returned as a form submission (typically used for servers that can handle POST requests more securely).

These parameters are integral to the OAuth process, but if not validated correctly, they can lead to vulnerabilities. For instance, attackers can exploit various OAuth parameter options in creative ways. For example, Omegapoint bypassed the patch for CVE-2023-6291 by utilizing response_mode=form_post instead of response_mode=fragment, which was user-controlled. This change caused the redirect to use an HTML form instead of a standard redirect. Due to insufficient validation on the HTML form’s action attribute, attackers were able to manipulate the redirect and send users to any arbitrary location, effectively bypassing security measures.

In a different instance, the prompt parameter can be manipulated to alter the authorization flow. By setting the prompt parameter to none, an attacker can effectively remove the requirement for user interaction during the authorization process. This means that if the victim is already authenticated, the authorization server may skip the consent screen and directly issue an authorization code.

In the next sections, we’ll explore a few of the most impactful security issues that can occur in OAuth.

Abusing redirect_uri for Account Takeover

The first and most common attack vector is using the redirect_uri parameter to redirect the victim to the attacker site with the victim’s authorization code.

If the client doesn’t configure a strict validation for the redirect_uri parameter, an attacker can exploit this by injecting their own URL, redirecting the user to a site they control. Imagine a scenario where an attacker sends a crafted OAuth initial request (e.g., https://github.com/login/oauth/authorize?redirect_uri=https://attacker.com) through a phishing attempt. The victim, unaware of the threat, clicks on the link and their authorization code is redirected to the attacker. The attacker can then use the OAuth client to exchange this code for an access token, potentially leading to account takeover.

Attacker website diagram

Figure 2 – OAuth Flow Redirecting the Victim to the Attacker Website

In the following example diagram (Figure 2) we demonstrate how an attacker can exploit the redirect_uri parameter to redirect the victim to a pre-determined URL without proper validation – this is known as an open redirect vulnerability. These are the following ways the attacker eventually takes over the victim’s account:

1. The attacker crafts a malicious link that exploits an open redirect vulnerability by embedding a malicious destination URL in the redirect_uri parameter (e.g., ?to=https://attacker.com).

2. The victim clicks the malicious link and requests an authorization code from GitHub (the authorization server).

3. GitHub issues the authorization code and sends it to the victim.

4. The victim is redirected to the OAuth client (example.com).

5. example.com follows the redirect and sends the victim a new link.

6. The victim, unknowingly, follows the new link, which redirects them to the attacker’s website along with the authorization code.

7. The attacker intercepts the authorization code and forwards it to the legitimate example.com callback endpoint (the original redirect_uri).

8. example.com sends the authorization code to GitHub to exchange it for an access token.

9. GitHub verifies the authorization code and sends the access token to the example.com.

10. Example.com uses the access token to request user information (such as Victim’s identity) from GitHub.

11. GitHub validates the access token and returns the user’s identity (e.g., “Victim”).

12. Example.com authenticates the victim based on the received identity and grants him access to his resources.

We showed here a classic attack scenario, however, if proof key for code exchange (PKCE) is implemented, it can effectively prevent this type of attack. PKCE is an extension to the OAuth 2.0 protocol that adds an extra layer of security by requiring the client to generate a unique code verifier and code challenge during the authorization process. This ensures that even if the attacker intercepts the authorization code, they cannot exchange it for an access token without the corresponding code verifier, thus protecting the user’s account from unauthorized access.

Interesting fact: most of the websites we checked didn’t use PKCE.

Preserving the Authorization Code After Redirection

In many cases, based on our research, even if an attacker succeeds in redirecting the user to a malicious server, the authorization code won’t be forwarded. This is because the authorization code is typically included as a query parameter (e.g., ?code=) which certain types of redirects may not retain. This behavior can add a layer of security by preventing the attacker from capturing the authorization code even if they successfully redirect the user.

To bypass this issue, the attacker can use hash fragment (or URI fragment), a portion of a URI that follows the # symbol. The reason for that is because when a redirect happens and the URI contains a hash (#), everything after (and including) the hash is stripped from the URI before the HTTP request is sent to the server — even though it remains visible in the browser’s address bar — as stated in RFC 3986 (page 24):

“…the fragment identifier is not used in the scheme-specific processing of a URI; instead, the fragment identifier is separated from the rest of the URI prior to a dereference, and thus the identifying information within the fragment itself is dereferenced solely by the user agent, regardless of the URI scheme.”

By preserving the authorization code within the fragment, the attacker can exploit this behavior by redirecting the victim to a malicious website that they control. Since JavaScript running on the attacker’s site can access window.location.hash, they can extract the authorization code directly from the browser’s address bar.

There are few ways to make the redirection use hash fragment:
1. Changing the value of response_mode parameter to fragment (demonstrated by Frans Rosen).
2. Changing the response_type to token, which returns the access token directly in the URI fragment (#).

1. In Facebook specifically, changing the response_type to code%20token (demonstrated by Aviad Carmel).

Keep in mind that these methods may not be effective in all implementations, as OAuth clients handle these parameters differently.

Challenges in Exploiting Redirect URIs

We noticed that most websites restrict the redirect_uri parameter to an absolute path, making it almost impossible to exploit since attackers cannot manipulate it to redirect users to malicious locations.

One of the tests in our research was to see if we can change the endpoints paths after the domain name.

For instance, we checked if we received an error after the following change:

  • Before: https://www.example.com/oauth/callback
  • After: https://www.example.com/any/callback

We found that 28 websites didn’t use absolute restrict path (due to the use of domain wildcards, e.g., “domain.com/*”), and 18 of them allowed us to append any path after the domain name (Figure 3). However, this behavior doesn’t allow us to change the original domain to our domain. This means we still need to find an open redirect vulnerability on the target website, as we can’t change the domain. We need a URL that shares the same domain.

Validation Across

Figure 3 – Redirect URI Validation Across 100 Websites

Methods to Bypass redirect_uri

We searched for more methods to bypass the redirect_uri validation by experimenting with various techniques to manipulate the parser. In the beginning we focused on bypassing the redirect_uri domain validation. If we can set our own domain, we can simply forward the authorization code without the need for open redirect vulnerability.

We collected some of the bypass techniques we found related to OAuth, some are based on real-world scenarios (e.g. bypass OAuth using IDN homograph), and created a list (Appendix A). To test them effectively we created “oauth-hunter” (Figure 4), an open-source security research tool that automates OAuth misconfigurations.

oauth-hunter

Figure 4 – oauth-hunter

Currently, most of the bypass techniques we attempted didn’t reveal numerous vulnerabilities. While there are real-world scenarios where these methods have been effective (e.g., bypass OAuth using IDN homograph), most of them didn’t work in our research.

This may be due to our primarily focus on popular IdPs like Facebook and GitHub, which have robust protections against these types of bypass techniques.

State Parameter Manipulation

The second issue we checked was the state parameter validation. As we mentioned in the beginning, the state parameter role is to prevent CSRF attacks. CSRF is a web-based attack that tricks a user into unknowingly submitting a malicious request, allowing an attacker to perform actions on behalf of the user without their consent or approval. In the context of OAuth, CSRF attacks can allow an attacker to exploit a user’s session to gain unauthorized access to protected resources.

We discovered that 21 of the websites we examined did not properly verify the state parameter. The state could be freely set and reused and in some cases even allow the attacker to craft an OAuth URL that could trick the user into unknowingly connecting to the attacker’s session. This causes the victim to be logged into the attacker’s account, unknowingly interacting within the attacker’s session. As a result, any action performed—such as entering personal data—can become accessible to the attacker, who can later retrieve and use this information.

Out of these, we identified eight websites that were actively vulnerable to this specific attack (Figure 5).

State Validation Across

Figure 5 – State Validation Across 100 Websites

 

The attack process was as follows:

1. Attacker intercepts an OAuth initial request:
The attacker initiates an OAuth request login process and intercepts the authorization code before exchanging it for an access token. Then they save the authorization code and drop the request.

2. Victim uses the attacker’s authorization code:

The attacker tricks the victim into starting the OAuth process using the attacker’s OAuth URL with the attacker’s authorization code. The victim connects to the attacker’s account.

A question that often arises is, “What is the problem?”

A real-world case study demonstrated by Salt Security showed how the attacker can craft a malicious link and cause the victim to install a custom malicious ChatGPT’s plug-in with the attacker’s session, allowing the attacker to gain access to the victim’s private chat data. Since the attacker controls the plugin, they can intercept sensitive information such as credentials or other personal details.

Pre-account TakeOver

Our final analysis focused on a vulnerability known as pre-account takeover. This type of vulnerability occurs when an attacker exploits a flaw in the authorization or registration process, allowing them to take control of a user’s account before the legitimate user completes their OAuth flow. Here’s how it could happen:

1. The attacker initiates a registration process using the victim’s email address, creating an account.

2. The victim later attempts to log in or sign up on the same website using the OAuth provider and their email address. In some cases, when the victim later connects using their OAuth provider, the website verifies the account but doesn’t detect that it was originally created by the attacker, allowing the attacker to get control.

3. Since the attacker has already created the account using the victim’s email, they can now log into the victim’s account without the victim knowing.

This vulnerability is generally not considered as high risk because it relies on a sequence of events that are unlikely to occur:

  • The attacker needs to know the victim’s email address and successfully create an account before the victim attempts to register.

As a result, this type of vulnerability is often categorized as low severity, since it doesn’t involve direct exploitation; the attacker must wait for the victim to log in, and the timing of this event is unpredictable.

We found seven websites that were vulnerable to pre-account takeover. (Figure 6).

Takeover Across

Figure 6 – Pre-account Takeover Across 100 Websites

Results

Looking at the OAuth configuration across 100 websites, it’s clear that while many are doing things right, there’s still room for improvement. The following table summarizes our findings throughout the blog (Figure 7).

In the following table we show:

  • All the validated websites for a specific category.
  • All the “Not Validated,” which are misconfiguration that are not exploitable.
  • The “Exploitable,” which are the ones we could exploit.

Misconfiguration Across

Figure 7 – All OAuth Misconfiguration Across 100 Websites

Insights and Recommendations

Most websites—at least according to our research—follow the best practices for OAuth configuration, reducing the attack surface significantly. However, there are still cases where websites didn’t follow the OAuth best practices, leaving them vulnerable to OAuth attacks.

We have learned a number of things from this research that can help make a more secured OAuth:

1. Configure the redirect_uri to an absolute path: Ensure that the redirect_uri parameter is set to an absolute URL to prevent potential redirection issues.

2. Use PKCE: Prevent the attacker from exchanging a stolen authorization code for an access token, even if the code is intercepted via an open redirect.

3. Validate the state : The state parameter should be unique for each session and validated to protect against CSRF attacks.

4. Prevent pre-account takeover by:

1. Verifying email ownership: Ensure users cannot sign up with an email address they do not own and require two-factor authentication (2FA) for email validation.

2. Handling IdP sign-ins securely: If a user signs in using an IdP, prevent subsequent sign-ins with the same email address or prompt the user to change their password for added security.

Final Thoughts and Next Steps

Our in-depth analysis of OAuth implementations across 100 websites has shed light on several security issues that can impact user safety and data integrity.

Despite the convenience OAuth offers for secure and seamless authentication, our research reveals that several sites still fall short in validating redirect_uri and state parameters, leading to potential account takeover. We introduced a new open-source security research tool “oauth-hunter” to help identify those kind of security issues.

Additionally, pre-account takeover vulnerabilities highlight the need for robust verification practices to prevent unauthorized access. Looking ahead, it’s really important for organizations to follow best practices and stay alert to these threats to keep their OAuth systems secure and reliable.

Eviatar Gerzi is a principal cyber researcher at CyberArk Labs.

References

Appendix – A

 
# https://www.youtube.com/watch?v=n9x7_J_a_7Q
https://target.com.attacker.com
https://[email protected]
//attacker.com
https://attacker.com\@target.com
https://[email protected]
https://attacker.com%0d%0atarget.com 

# https://i.blackhat.com/asia-19/Fri-March-29/bh-asia-Wang-Make-Redirection-Evil-Again-wp.pdf
## Over-consumption
https://attacker%[email protected]
https://attacker%ff.target.com
## Decode to question mark
### origin: https://hackerone.com/reports/108113
https://attacker.com%[email protected]
https://attacker.com%bf:@target.com
## Best fit mappings
https://attacker.com/.target.com
https://target.com/@attacker.com
## Slash trick
https://attacker.com\@target.com
https://target.com\@attacker.com

## Scheme Manipulation
4ttacker.com://target.com

## IPv6 Address Parsing Bug
https://attacker.com\[target.com]

## Combined Validator
https://target.com.attacker.com\@target.com

# Reference: https://dl.acm.org/doi/fullHtml/10.1145/3627106.3627140
https://target.com/callback/attacker.com
https://target.com/callback%2Fattacker.com
https://target.com/callback/..%2Fattacker.com
https://target.com/callback/%2e%2e%2Fattacker.com
https://target.com/callback/..%252Fattacker.com
https://target.com/callback/%252e%252e%252Fattacker.com
https://target.com/callback/attacker.com/..
https://target.com/callback%2Fattacker.com%2F..
https://target.com/callback%2Fattacker.com%2F%2e%2e
https://target.com/callback%252Fattacker.com%252F..
https://target.com/callback%252Fattacker.com%252F%252e%252e
https://target.com/callback/;/../../attacker.com
https://target.com/callback/%3B/../../attacker.com
https://target.com/callback/%3B%2F..%2F..%2Fattacker.com
https://target.com/callback/%3B%2F%2e%2e%2F%2F%2e%2eattacker.com
https://target.com/callback/%253B%252F..%252F..%252Fattacker.com
https://target.com/callback/%0A%0D/../../attacker.com
https://target.com/callback/%0A%0D%2F..%2F..%2Fattacker.com
https://target.com/callback/%0A%0D%2F%2e%2e%2F%2F%2e%2eattacker.com
https://target.com/callback/%250A%250D%252F..%252F..%252Fattacker.com

# Reference: https://github.com/snyff/oauthsecurity
https://target.com/old/path/../../new/path
https://target.com/old/path/%2e%2e/%2e%2e/new/path
https://target.com/old/path/%252e%252e/%252e%252e/new/path
https://target.com/new/path///../../old/path/
https://target.com/old/path/.%0a./.%0d./new/path (For Rails, because it strips \n\d\0)

# Bypass OAuth using IDN homograph
# Reference: https://hackerone.com/reports/861940
https://targét.com
https://targêt.com
https://targèt.com

# https://nbsriharsha.blogspot.com/2016/04/oauth-20-redirection-bypass-cheat-sheet.html?m=1
http://attacker.com%2f%2f.target.com
http://attacker.com%5c%5c.target.com
http://attacker.com%3F.target.com
http://attacker.com%23.target.com
http://target.com:80%40attacker.com
http://target.com%2eattacker.com

Previous Article
Let’s Be Authentik: You Can’t Always Leak ORMs
Let’s Be Authentik: You Can’t Always Leak ORMs

Introduction Identity providers (IdPs) or Identity and Access Management (IAM) solutions are essential for ...

Next Article
Teach Yourself Kubiscan in 7 Minutes (or Less…)
Teach Yourself Kubiscan in 7 Minutes (or Less…)

While Kubernetes’ Role-based access control (RBAC) authorization model is an essential part of securing Kub...