An offensive guide to the Authorization Code grant

OAuth is the widely used standard for access delegation, enabling many of the “Sign in with X” buttons and “Connect your Calendar” features of modern Internet software. OAuth 2.0 is the most common and recent version of this specification, which defines four grant types (as well as various extensions), specifically suited for different use cases.

As a security consultancy, we frequently encounter OAuth flows as one small component of the assessment of an application. As a result, we developed the desire for a comprehensive and digestible enumeration of security concerns in the OAuth 2.0 Authorization Code flow, from an end-user (or penetration tester)’s external vantage.

This post will introduce, break down the observable vulnerabilities, and explain the exploitation of each the following aspects of the Authorization Code flow:

  1. state
  2. code
  3. redirect_url
  4. client_secret
  5. Access Token
  6. Clickjacking

While numerous resources have been published on OAuth 2.0 security, none we found met our specific needs. Check out all the linked references, as they overlap with much of the information in this post, as well as a deeper dive into other aspects of OAuth 2.0 security!

Sidebar: While this post is discussing the Authorization Code grant, it still bears mention to keep an eye out for any usage of the Implicit flow or Password flow – these are legacy and considered generally insecure.


Let’s start with a brief history of OAuth:
2006: OAuth is started to fix an identified lack of an “open standard for API access delegation”
2007: The first draft of the OAuth specification is released
2009: A session fixation attack is discovered and disclosed
2009: OAuth 1.0a is issued to address the vulnerability
2010: OAuth 1.0 is published as RFC 5849
2012: OAuth 2.0 is published as RFC 6749, it is not backwards compatible with 1.0
2012-2013: OAuth 2.0 Threat Model and Security Considerations is developed as RFC 6819
2017-: OAuth 2.0 Security Best Current Practice has been through 15 drafts

We most commonly encounter the Authorization Code grant on client engagements. It is generally used by both web and native applications to retrieve an access token after a user authenticates to the third-party app. Generally, an app using this grant type will launch a browser to begin the flow.

Terminology

We will be using the formal terminology from the OAuth 2.0 specification throughout this post. A detailed breakdown of the terminology can be found on the oauth.com Terminology Reference:

The Resource Owner “the user”: This is the person who is granting some level of access to their account, data, services, or other access controlled resources.

The Resource Server “the API”: This is the server that contains the Resource Owner‘s data, which the third-party application is looking to access.

The Client “the application”: This is the application that will be accessing protected resources from the Resource Server on behalf of the Resource Owner.

Authorization Server: The server issuing access tokens to the Client. This is the server that generally displays the OAuth prompt, allowing the user to accept or deny the request.

Authorization Code grant flow

In the Authorization Code grant, the Client ultimately exchanges an authorization code for an access token. To start this flow, the Resource Owner makes a request to the Client. The Client then redirects the Resource Owner to the Authorization Server, passing a client_id, state, and redirect_uri. After the Resource Owner authenticates, the redirect_uri is passed by the Authorization Server back to the Resource Owner along with a code. The Resource Owner then passes the code to the redirect_uri (which should be on the Client). The Client can then use the code, in conjunction with the client_id and client_secret to retrieve the access code.

The security benefit of this grant type includes the ability to authenticate the Client (via the client_secret), and the fact that the access token is never directly exposed or transmitted to the Resource Owner, limiting the chances of its compromise.

A detailed diagram of the flow, from “A Comprehensive Formal Security Analysis of OAuth 2.0” (Fett, Küsters, Schmitz) follows. Note, in the diagram the Resource Owner is referred to as the Browser, the Client as “RP,” and the Authorization Server as “IdP”:

OAuth 2.0 Authorization Code grant flow

Sidebar: Want another explanation of the flow? Go checkout Okta’s “Illustrated Guide” and then circle back here!

Attacking OAuth 2.0

Here are the common attack vectors against an OAuth 2.0 Authorization Code grant flow, presented according to the aspect of the flow which they target. Some of these issues arise from a lack of specificity in the original OAuth 2.0, while others represent common patterns of misimplementation. These vulnerabilities may be introduced by either the Client or the Authorization Server, however that has little bearing on their initial identification and exploitation:

state:

Usage:

The state parameter was introduced to mitigate CSRF attacks. While it is RECOMMENDED but not REQUIRED in the specification, it should always be used. The state functions as follows:

1. The Client generates a random string, and it is included the authorization request as the state parameter. This value should also be stored in a location accessible only to the Resource Owner – commonly as a cookie or in browser local storage.
2. The Authorization Server redirects the Resource Owner back to the Client, and includes the exact provided state.
3. The Client must verify the validity of the request by matching the returned state to the Resource Owners’s stored value.

Exploitation:

To exploit any issue with state, you’ll be performing a CSRF attack. In short:

  1. An attacker can go through the grant flow, dropping the final request by the Resource Owner (where the code is presented to the Client)
  2. As in a CSRF attack, the attacker must coerce the victim’s browser into sending the dropped request in their stead
  3. Once the victim has sent the request, their account on the resource provider will be associated with your account on the authorization provider, frequently leading to account takeover

What to look for:

There are a number of common implementation flaws for the state parameter in OAuth 2.0. When testing an OAuth 2.0 flow, always check:

  • Lack of state – as described above, the state parameter is mandatory to prevent CSRF attacks. If you observe an OAuth 2.0 Authorization Code grant flow that entirely lacks this parameter, it is highly likely to be vulnerable.
  • state not required – try to simply drop the state parameter from the authorization request, and make sure the application is properly mandating its usage.
  • state not validated – try modifying the state provided by the client to the resource provider, to ensure the client validates it matches the original provided state.
  • Predictable state – often, applications misuse the state parameter to pass values through the flow. One use case for this is having state partially or fully determine the redirect. While this can be done correctly (generally by appending the data to the random state), it can result in a predictable state.
  • state fixation – while rare, we have observed applications that allow a user-provided state to initialize the OAuth 2.0 flow. This be exploited by an attacker who first fixes the state, and then coerces the victim to enter the OAuth 2.0 with the now-predictable state.

Sidebar: The PKCE extension to OAuth 2.0 can be used in place of the state parameter. PKCE is defined in RFC 7636.

code:

Usage:

The code parameter contains the authorization code received from the authorization server by the Resource Owner. The Resource Owner then presents this to the Client, which can use it to retrieve an access_token from the Authorization Server. The access_token can then be used to access the protected resources on the Resource Server.

Exploitation:

Generally, issues identified with the code require a second vulnerability, exposing the code, to exploit. See the discussion of redirect_uri exploitation below.

What to look for:

The most common vulnerability found with code implementations is susceptibility to replay attacks. Consider the following test cases to ensure the flow is hardened against such attacks:

  • Limited validity period: The code should only be usable for 5 to 10 minutes for the time of issue.
  • Reuse: The code should only be usable once. Consider race conditions on submission as well.
  • replay attacks: Upon attempted reuse of the code all access tokens previously issues based on the code should be revoked, as it is an indicator of code compromise.

redirect_uri:

Usage:

As per the spec, “After completing its interaction with the resource owner, the authorization server directs the resource owner’s user-agent back to the client. The authorization server redirects the user-agent to the client’s redirection endpoint previously established with the authorization server during the client registration process or when making the authorization request” 

Exploitation:

Exploitation of the redirect_uri relies on manipulating providing a “poisoned” authorization request to the victim, which contains an attacker-controlled domain as the redirect URL. Once the victim authenticates, they will be redirected to the attacker-controlled domain. This redirect will include the cod, which the attacker can then us to access the victim’s account.

If code reuse is also allowed, this could in-fact be done transparently, as an attacker could simultaneously gain a session to the victim’s account, and also authenticate the victim.

What to look for:

The redirect_uri is likely the most exploited aspect of the OAuth 2.0 flow. Consider the following cases when testing an OAuth 2.0 flow:

  • No validation on redirect_uri: you will be able to exploit with attacker.com as the redirect_uri
  • Partial validation on redirect_uri domain: applications have endless implementations of validation for redirect_uri, the following are common lapses in that validation:
    1. evilmatch.com – application fails to anchor the domain pattern at the beginning of the domain
    2. match.com.evil.com: application fails to anchor the domain pattern at the end of the domain
    3. evil.com#match.com, evil.com?match.com: application fails to properly parse URL
    4. matchAmatch.com – application fails to escape wildcard in pattern that includes subdomain.
    5. match.com.mx: application fails to validate full TL
  • IDN homograph normalization: some applications inconsistently normalize IDN homographs, using the normalized version for validation, but redirecting to the original domain. (ex. https://hackerone.com/reports/861940)
  • Arbitrary paths allowed in redirect_uri: in this case, there are a number of vulnerabilities or cases that can be chained with the redirect_uri, including:
    1. Cross-site scripting
    2. Open redirect
    3. Page with a user-controlled third-party inclusion (such as a user profile with link to a photo). This can be mitigated if the site strips the Referer header.
  • Partial validation on paths allowed in redirect_uri: directory traversal tricks can be used to bypass validation on the path in the redirect_uri.
  • Arbitrary subdomains allowed in redirect_uri: subdomain takeovers can lead to compromise. If arbitrary paths are also allowed, any of the vulnerabilities discussed in (3), on any subdomain, can be used.
  • redirect_uri allows cleartext protocols: if the redirect_url does not properly require https, the OAuth flow is opened to exploitation in the case of a MITM attack.

client_secret

Usage:

In the OAuth 2.0 specification of the Authorization Code grant, the client_secret is a value unique to every resource provider, that allows it to valid codes (with state, if required) for access tokens.

Exploitation:

The Authorization Code grant type was not initial designed for use in mobile or native applications, which frequently have no way to securely store the client_secret. If the secret is leaked, an attacker who compromises a valid code can escalate access by retrieving an access token for that user.

Sidebar: The PKCE extension effectively removes the need for a client_secret. For a full discussion of PKCE, see Pragmatic Web Security’s blog post.

What to look for:

An application using the Authorization Code grant must keep the client_secret confidential or use PKCE. If a mobile application or native application is using this grant type, you can likely retrieve the client_secret from the application package.

Access Token

In the Authorization Code grant flow, the access token should never be exposed to the client, only used by the resource provider. Some common places the access token is accidentally disclosed include:

  1. In requests, stored in the browser history
  2. In transit, accessible to a client who is proxying traffic
  3. In JavaScript objects or state
  4. In browser local storage

The Authorization Code grant is designed to enable the Client to opaquely use the access token, without ever exposing it to the Resource Owner. This is built into the specification to reduce the chance of access token compromise. Any exposure of the access token is therefore a direct violation of the specification.

Clickjacking

While somewhat perpendicular to the actual Authorization Code grant flow, clickjacking is a major threat, and has been highlighted in both the Security Best Current Practice draft, as well as in the Threat Model. Authorization servers must prevent clickjacking attacks. The “X-Frame-Options” header is the legacy mitigation for clickjacking, while the Content Security Policy frame-ancestors directive is a more modern control.

Sidebar: Check out our Clickjacking PoC tool. See here for a case where a $5,000 bug bounty was paid for this issue.

Thank you to Victor Magierski for assistance in editing this post!

References: