TryHackMe: OAuth Vulnerabilities

OAuth Grant Types Authorisation Grant Type The Authorization Code grant is the most commonly used OAuth 2.0 flow suited for server-side applications (PHP, JAVA, .NET etc). In this flow, the client redirects the user to the authorization server, where the user authenticates and grants authorization. The authorization server then redirects the user to the client with an authorization code. The client exchanges the authorization code for an access token by requesting the authorization server's token endpoint. This grant type is known for its enhanced security, as the authorization code is exchanged for an access token server-to-server, meaning the access token is not exposed to the user agent (e.g., browser), thus reducing the risk of token leakage. It also supports using refresh tokens. Implicit Grant The Implicit grant is primarily designed for mobile and web applications where clients cannot securely store secrets. It directly issues the access token to the client without requiring an authorization code exchange. In this flow, the client redirects the user to the authorization server. After the user authenticates and grants authorization, the authorization server returns an access token in the URL fragment. This grant type is simplified and suitable for clients who cannot securely store client secrets. It is faster as it involves fewer steps than the authorization code grant. However, it is less secure as the access token is exposed to the user agent and can be logged in the browser history. It also does not support refresh tokens. Resource Owner Password Credentials Grant The Resource Owner Password Credentials grant is used when the client is highly trusted by the resource owner, such as first-party applications. The client collects the user’s credentials (username and password) directly and exchanges them for an access token. In this flow, the user provides their credentials directly to the client. The client then sends the credentials to the authorization server, which verifies the credentials and issues an access token. This grant type is direct, requiring fewer interactions, making it suitable for highly trusted applications where the user is confident in providing their credentials. However, it is less secure because it involves sharing credentials directly with the client and is unsuitable for third-party applications. Client Credentials Grant The Client Credentials grant is used for server-to-server interactions without user involvement. The client uses his credentials to authenticate with the authorization server and obtain an access token. In this flow, the client authenticates with the authorization server using its client credentials (client ID and secret), and the authorization server issues an access token directly to the client. This grant type is suitable for backend services and server-to-server communication as it does not involve user credentials, thus reducing security risks related to user data exposure. The Flow of OAuth The OAuth 2.0 flow begins when a user (Resource Owner) interacts with a client application (Client) and requests access to a specific resource. The client redirects the user to an authorization server, where the user is prompted to log in and grant access. If the user consents, the authorization server issues an authorization code, which the client can exchange for an access token. This access token allows the client to access the resource server and retrieve the requested resource on behalf of the user. Below shows an example of OAuth: Authorization Request http://coffee.thm:8000/accounts/login/?next=/o/authorize/%3Fclient_id%3Dzlurq9lseKqvHabNqOc2DkjChC000QJPQ0JvNoBt%26response_type%3Dcode%26redirect_uri%3Dhttp%3A//bistro.thm%3A8000/oauthdemo The link above is retrieved by pressing the login option of a website. response_type=code: This indicates that CoffeeShopApp is expecting an authorization code in return. state: A CSRF token to ensure that the request and response are part of the same transaction. client_id: A public identifier for the client application, uniquely identifying CoffeeShopApp. redirect_uri: The URL where the authorization server will send Tom after he grants permission. This must match one of the pre-registered redirect URIs for the client application. scope: Specifies the level of access requested, such as viewing coffee orders. Below is an example Python code to get such URL. def oauth_login(request): app = Application.objects.get(name="CoffeeApp") redirect_uri = request.GET.get("redirect_uri", "http://bistro.thm:8000/oauthdemo/callback") authorization_url = ( f"http://coffee.thm:8000/o/authorize/?client_id={app.client_id}&response_type=code&redirect_uri={redirect_uri}" ) return redirect(authorization_url) Authorisation and Authentication The process typically involves: User Login: Tom e

Mar 13, 2025 - 17:04
 0
TryHackMe: OAuth Vulnerabilities

OAuth

Grant Types

Authorisation Grant Type

The Authorization Code grant is the most commonly used OAuth 2.0 flow suited for server-side applications (PHP, JAVA, .NET etc). In this flow, the client redirects the user to the authorization server, where the user authenticates and grants authorization. The authorization server then redirects the user to the client with an authorization code. The client exchanges the authorization code for an access token by requesting the authorization server's token endpoint.

Image description

This grant type is known for its enhanced security, as the authorization code is exchanged for an access token server-to-server, meaning the access token is not exposed to the user agent (e.g., browser), thus reducing the risk of token leakage. It also supports using refresh tokens.

Implicit Grant

The Implicit grant is primarily designed for mobile and web applications where clients cannot securely store secrets. It directly issues the access token to the client without requiring an authorization code exchange. In this flow, the client redirects the user to the authorization server. After the user authenticates and grants authorization, the authorization server returns an access token in the URL fragment.

Image description

This grant type is simplified and suitable for clients who cannot securely store client secrets. It is faster as it involves fewer steps than the authorization code grant. However, it is less secure as the access token is exposed to the user agent and can be logged in the browser history. It also does not support refresh tokens.

Resource Owner Password Credentials Grant

The Resource Owner Password Credentials grant is used when the client is highly trusted by the resource owner, such as first-party applications. The client collects the user’s credentials (username and password) directly and exchanges them for an access token.

Image description

In this flow, the user provides their credentials directly to the client. The client then sends the credentials to the authorization server, which verifies the credentials and issues an access token. This grant type is direct, requiring fewer interactions, making it suitable for highly trusted applications where the user is confident in providing their credentials. However, it is less secure because it involves sharing credentials directly with the client and is unsuitable for third-party applications.

Client Credentials Grant

The Client Credentials grant is used for server-to-server interactions without user involvement. The client uses his credentials to authenticate with the authorization server and obtain an access token. In this flow, the client authenticates with the authorization server using its client credentials (client ID and secret), and the authorization server issues an access token directly to the client.

Image description

This grant type is suitable for backend services and server-to-server communication as it does not involve user credentials, thus reducing security risks related to user data exposure.

The Flow of OAuth

The OAuth 2.0 flow begins when a user (Resource Owner) interacts with a client application (Client) and requests access to a specific resource. The client redirects the user to an authorization server, where the user is prompted to log in and grant access. If the user consents, the authorization server issues an authorization code, which the client can exchange for an access token. This access token allows the client to access the resource server and retrieve the requested resource on behalf of the user.

Image description

Below shows an example of OAuth:

  1. Authorization Request

http://coffee.thm:8000/accounts/login/?next=/o/authorize/%3Fclient_id%3Dzlurq9lseKqvHabNqOc2DkjChC000QJPQ0JvNoBt%26response_type%3Dcode%26redirect_uri%3Dhttp%3A//bistro.thm%3A8000/oauthdemo

The link above is retrieved by pressing the login option of a website.

  • response_type=code: This indicates that CoffeeShopApp is expecting an authorization code in return.
  • state: A CSRF token to ensure that the request and response are part of the same transaction.
  • client_id: A public identifier for the client application, uniquely identifying CoffeeShopApp.
  • redirect_uri: The URL where the authorization server will send Tom after he grants permission. This must match one of the pre-registered redirect URIs for the client application.
  • scope: Specifies the level of access requested, such as viewing coffee orders.

Below is an example Python code to get such URL.

def oauth_login(request):
    app = Application.objects.get(name="CoffeeApp")
    redirect_uri = request.GET.get("redirect_uri", "http://bistro.thm:8000/oauthdemo/callback")

    authorization_url = (
        f"http://coffee.thm:8000/o/authorize/?client_id={app.client_id}&response_type=code&redirect_uri={redirect_uri}"
    )
    return redirect(authorization_url)
  1. Authorisation and Authentication

The process typically involves:

  • User Login: Tom enters his username and password on the authorization server's login page.
  • Consent Prompt: After authentication, the authorization server presents Tom with a consent screen detailing what CoffeeShopApp requests access to (e.g., viewing his coffee orders). Tom must then decide whether to grant or deny these permissions.
  1. Authorization Response

If Tom agrees to grant access, the authorization server generates an authorization code (as also discussed in Task 4). The server then redirects Tom to the bistro website using the specified redirect_uri. The redirection includes the authorization code and the original state parameter to ensure the integrity of the flow.

The authorization server responds with the following:

  • code: CoffeeShopApp will use the authorisation code to request an access token.
  • state: The CSRF token previously sent by CoffeeShopApp to validate the response.

An example authorization response would be https://bistro.thm:8000/callback?code=AuthCode123456&state=xyzSecure123.

This step ensures the authorization process is secure and the response is linked to the bistro's initial request. The authorization code is a temporary token that will be used in the next step to obtain an access token, allowing CoffeeShopApp to access Tom's profile details.

  1. Token Request

The bistro website exchanges the authorization code for an access token by requesting the authorization server’s token endpoint through a POST request with the following parameters:

  • grant_type: type of grant being used; usually, it's set as code to specify authorization code as the grant type.
  • code: The authorization code received from the authorization server.
  • redirect_uri: This must match the original redirect URI provided in the authorization request.
  • client_id and client_secret: Credentials for authenticating the client application.

Using the above parameters, the following code will make a token request to /o/token endpoint.

token_url = "http://coffee.thm:8000/o/token/"
    client_id = Application.objects.get(name="CoffeeApp").client_id
    client_secret = Application.objects.get(name="CoffeeApp").client_secret
    redirect_uri = request.GET.get("redirect_uri", "http://bistro.thm:8000/oauthdemo/callback")

    data = {
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": redirect_uri,
        "client_id": client_id,
        "client_secret": client_secret,
    }

    headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': f'Basic {base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()}',
    }

    response = requests.post(token_url, data=data, headers=headers)
    tokens = response.json()

If everything is correct, the authorization server will respond with the access token, allowing the bistro website to proceed with accessing Tom's profile details.

  1. Token Response

The authorization server authenticates the bistro website and validates the authorization code. Upon successful validation, the server responds with an Access Token and, optionally, a Refresh Token.

The authorization server's response includes the following:

  • access_token: Token that will be used to access Tom's details.
  • token_type: Typically "Bearer".
  • expires_in: The duration in seconds for which the access token is valid.
  • refresh_token (optional): A token used to obtain new access tokens without requiring the user to log in again.

Now, the bistro website can make authenticated requests to the resource server to retrieve Tom's profile. Each request to the resource server includes the access token in the authorization header, ensuring that the server recognizes and permits the access.

Identifying OAuth Usage

The first indication that an application uses OAuth is often found in the login process. Look for options allowing users to log in using external service providers like Google, Facebook, and GitHub. These options typically redirect users to the service provider's authorization page, which strongly signals that OAuth is in use.

Detecting OAuth Usage

The URL often contains specific query parameters, such as response_type, client_id, redirect_uri, scope, and state. These parameters are indicative of an OAuth flow in progress. For example, a URL might look like this:

https://dev.coffee.thm/authorize?response_type=code&client_id=AppClientID&redirect_uri=https://dev.coffee.thm/callback&scope=profile&state=xyzSecure123

Identifying the OAuth Framework

Once you have confirmed that OAuth is being used, the next step is to identify the specific framework or library the application employs. This can provide insights into potential vulnerabilities and the appropriate security assessments. Here are some strategies to identify the OAuth framework:

  • HTTP Headers and Responses: Inspect HTTP headers and response bodies for unique identifiers or comments referencing specific OAuth libraries or frameworks.
  • Source Code Analysis: If you can access the application's source code, search for specific keywords and import statements that can reveal the framework in use. For instance, libraries like django-oauth-toolkit, oauthlib, spring-security-oauth, or passport in Node.js, each have unique characteristics and naming conventions.
  • Authorization and Token Endpoints: Analyze the endpoints used to obtain authorization codes and access tokens. Different OAuth implementations might have unique endpoint patterns or structures. For example, the Django OAuth Toolkit typically follows the pattern, /o/authorize/, /oauth/authorize/ and /oauth/token/, while other frameworks might use different paths.
  • Error Messages: Custom error messages and debug output can inadvertently reveal the underlying technology stack. Detailed error messages might include references to specific OAuth libraries or frameworks.

Exploiting OAuth - Stealing OAuth Tokens

If the redirect_uri is not well protected, attackers can exploit it to hijack tokens.

The redirect_uri parameter is specified during the OAuth flow to direct where the authorization server should send the token after authorization. This URI must be pre-registered in the application settings to prevent open redirect vulnerabilities. During the OAuth process, the server checks that the provided redirect_uri matches one of the registered URIs.

An insecure redirect_uri can lead to severe security issues. If attackers gain control over any domain or URI listed in the redirect_uri, they can manipulate the flow to intercept tokens. Example of registered redirect URIs:

Image description

  • Attacker's Strategy: If an attacker gains control over dev.bistro.thm, they can exploit the OAuth flow. By setting the redirect_uri to http://dev.bistro.thm/callback, the authorization server will send the token to this controlled domain.
  • Crafted Attack: The attacker initiates an OAuth flow and ensures the redirect_uri points to their controlled domain. After the user authorizes the application, the token is sent to http://dev.bistro.thm/callback. The attacker can now capture this token and use it to access protected resources.

For this exercise, we assume that the attacker has compromised the domain dev.bistro.thm:8002 and can host any HTML page on the server. Consider Tom, a victim to whom we will send a link. The attacker can craft a simple HTML page (redirect_uri.html) with the following code:

This form sends a hidden redirect_uri parameter with the value http://dev.bistro.thm:8002/malicious_redirect.html and submits a request to http://coffee.thm:8000/oauthdemo/oauth_login/. The malicious_redirect.html page then intercepts the authorization code from the URL using the following code:


The attacker can send Tom the link (http://dev.bistro.thm:8002/redirect_uri.html) through social engineering tactics or a CSRF attack. The victim, unsuspecting of the malicious intent, clicks on the link, which takes them to the URL dev.bistro.thm:8002/redirect_uri.html.

In the attached VM, when the victim clicks the "Login via OAuth" button, the form calls to http://coffee.thm:8000/oauthdemo/oauth_login/ but with a falsified redirect_uri. Once the victim enters his credentials (victim:victim123) for the OAuth provider, it directs the OAuth authorization code to the attacker's controlled URL (http://dev.bistro.thm:8002/malicious_redirect.html), allowing the attacker to intercept and misuse the authorization code.

From the attacker’s machine, they can utilize can utilize the intercepted authorization code to call the /callback endpoint and exchange it for a valid access token. In an OAuth flow, as we saw earlier, the /callback endpoint is always available, accepting the code parameter and returning an access token. With this token, the attacker gains unauthorized access to the user's protected resources. To get the access token in this example, visit the URL http://bistro.thm:8000/oauthdemo/callbackforflag/?code=xxxxx and replace the code parameter with the acquired authorization code.

Exploiting OAuth - CSRF in OAuth

The state parameter in the OAuth 2.0 framework protects against CSRF attacks, which occur when an attacker tricks a user into executing unwanted actions on a web application where they are currently authenticated.

CSRF can occur when the state parameter is either missing or predictable (e.g., a static value like "state" or a simple sequential number).

Spotting the Vulnerability

Whenever we use the access token to do something account specific, such as syncing contacts between platforms, observe the OAuth Authorisation Server URL.

http://coffee.thm:8000/o/authorize/?response_type=code&client_id=kwoy5pKgHOn0bJPNYuPdUL2du8aboMX1n9h9C0PN&redirect_uri=http%3A%2F%2Fcoffee.thm%2Fcsrf%2Fcallbackcsrf.php

If the URL does not have the state parameter in the URL, we can exploit it.

Preparing the Payload

To prepare the payload, the attacker must get his authorization code. This can be done by intercepting the authorization process using a tool like Burp Suite or any other network interception tool. The code for the flow is pretty straight forward as shown below:

def oauth_logincsrf(request):
    app = Application.objects.get(name="ContactApp")
    redirect_uri = request.POST.get("redirect_uri", "http://coffee.thm/csrf/callbackcsrf.php") 

    authorization_url = (
        f"http://coffee.thm:8000/o/authorize/?client_id={app.client_id}&response_type=code&redirect_uri={redirect_uri}"
    )
    return redirect(authorization_url)

def oauth_callbackflagcsrf(request):
    code = request.GET.get("code")

    if not code:
        return JsonResponse({'error': 'missing_code', 'details': 'Missing code parameter.'}, status=400) 

    if code:
        return JsonResponse({'code': code, 'Payload': 'http://coffee.thm/csrf/callbackcsrf.php?code='+code}, status=400) 

Once the attacker has obtained the authorization code, he can prepare the CSRF payload. Suppose the attacker sends an email to the victim with a link like http://bistro.thm:8080/csrf/callbackcsrf.php?code=xxxx

Exploiting OAuth: Implicit Grant Flow

In the implicit grant flow, tokens are directly returned to the client via the browser without requiring an intermediary authorization code. This flow is primarily used by single-page applications and is designed for public clients who cannot securely store client secrets.

Weaknesses

  • Exposing Access Token in URL: The application redirects the user to the OAuth authorization endpoint, which returns the access token in the URL fragment. Any script running on the page can easily access this fragment.
  • Inadequate Validation of Redirect URIs: The OAuth server does not adequately validate the redirect URIs, allowing potential attackers to manipulate the redirection endpoint.
  • No HTTPS Implementation: The application does not enforce HTTPS, which can lead to token interception through man-in-the-middle attacks.
  • Improper Handling of Access Tokens: The application stores the access token insecurely, possibly in localStorage or sessionStorage, making it vulnerable to XSS attacks.

Deprecation of Implicit Grant Flow

Due to these vulnerabilities, the OAuth 2.0 Security Best Current Practice recommends deprecating the implicit grant flow in favour of the authorization code flow with Proof Key for Code Exchange (PKCE). This updated flow provides enhanced security by mitigating the risks of token exposure and lack of client authentication.

When access token is exposed in URL, we can exploit it as follows:

Once the victim is logged in, it redirects him/her to the page with html code below to get status and submit it via AJAX request:


Submitted Status

    $status]); exit(); } // Display previously stored statuses if (isset($_SESSION['statuses'])) { foreach ($_SESSION['statuses'] as $status) { echo '
  • ' . $status . '
  • '; } } ?>

Attacker will share the following payload as status:


Meanwhile attacker starts a listener at port 8081.

Let's dissect the payload:

  • The JavaScript payload starts by extracting the fragment identifier from the URL, which is the part of the URL following the # symbol. It removes the leading # using substr(1) to obtain the raw fragment string.
  • This string is then split by & to separate the individual key-value pairs. The reduce function processes each of these pairs, splitting them further by = to isolate the keys and values. These key-value pairs are then stored in an object called result.
  • The script extracts the access_token value from this object and assigns it to the variable accessToken. To exfiltrate this access token, the script creates a new Image object and sets its src attribute to a URL that points to an attacker's server (http://ATTACKBOX_IP:8081/steal_token), appending the access token as a query parameter.
  • When the image is loaded, it triggers a request to the attacker's server with the stolen access token included in the URL, effectively sending the token to the attacker.

If the attack is successful, attacker can use the access token to access the account accordingly.

Other OAuth Vulnerabilities

Insufficient Token Expiry

Problem: Access tokens with long or infinite lifetimes are risky because if an attacker obtains one, they can access protected resources indefinitely.

Solution: Use short-lived access tokens and refresh tokens to limit the time an attacker can misuse a token.

Replay Attacks

Problem: In a replay attack, attackers capture valid tokens and reuse them to gain unauthorized access.

Solution:

  • Use nonce values and timestamp checks to ensure tokens are only used once.
  • This helps prevent attackers from reusing stolen tokens.

Insecure Storage of Tokens

Problem: Storing tokens insecurely (e.g., in local storage or unencrypted files) can lead to token theft.

Solution:

  • Use secure storage options like secure cookies or encrypted databases to protect tokens from malicious access.

Evolution of OAuth 2.1

OAuth 2.1 is the latest version of the OAuth framework, designed to fix security flaws in OAuth 2.0 and improve best practices.

Why OAuth 2.1?

  • OAuth 2.0 had vulnerabilities and lacked security improvements that became apparent over time.
  • OAuth 2.1 addresses these weaknesses to provide a more secure and interoperable framework.

OAuth Evolution

Major Changes in OAuth 2.1

1. Deprecation of Implicit Grant Type

  • Problem: The implicit grant type exposed tokens in the URL, making them vulnerable.
  • Solution: OAuth 2.1 removes this and recommends using the authorization code flow with PKCE for public clients.

2. State Parameter for CSRF Protection

  • Problem: Cross-Site Request Forgery (CSRF) attacks can hijack authorization processes.
  • Solution: OAuth 2.1 mandates using the state parameter to prevent CSRF attacks.

3. Secure Token Handling and Storage

  • Problem: Storing tokens insecurely (e.g., in local storage) increases the risk of XSS attacks.
  • Solution: OAuth 2.1 recommends using secure cookies for token storage instead.

4. Improved Interoperability

  • Solution: OAuth 2.1 provides better guidelines for:
    • Redirect URI validation
    • Client authentication
    • Scope validation

For more detailed information on OAuth 2.1, you can refer to the official specification here.