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:
- Access Token
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.
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
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_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”:
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 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
3. The Client must verify the validity of the request by matching the returned
state to the Resource Owners’s stored value.
To exploit any issue with state, you’ll be performing a CSRF attack. In short:
- An attacker can go through the grant flow, dropping the final request by the Resource Owner (where the code is presented to the Client)
- As in a CSRF attack, the attacker must coerce the victim’s browser into sending the dropped request in their stead
- 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
stateparameter 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.
statenot required – try to simply drop the
stateparameter from the authorization request, and make sure the application is properly mandating its usage.
statenot validated – try modifying the
stateprovided by the client to the resource provider, to ensure the client validates it matches the original provided state.
state– often, applications misuse the
stateparameter to pass values through the flow. One use case for this is having
statepartially 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.
statefixation – while rare, we have observed applications that allow a user-provided
stateto 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.
PKCE extension to OAuth 2.0 can be used in place of the
PKCE is defined in RFC 7636.
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.
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
codeshould only be usable for 5 to 10 minutes for the time of issue.
- Reuse: The
codeshould only be usable once. Consider race conditions on submission as well.
- replay attacks: Upon attempted reuse of the
codeall access tokens previously issues based on the
codeshould be revoked, as it is an indicator of
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 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.
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:
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
- Partial validation on
redirect_uridomain: applications have endless implementations of validation for
redirect_uri, the following are common lapses in that validation:
evilmatch.com– application fails to anchor the domain pattern at the beginning of the domain
match.com.evil.com: application fails to anchor the domain pattern at the end of the domain
evil.com?match.com: application fails to properly parse URL
matchAmatch.com– application fails to escape wildcard in pattern that includes subdomain.
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
- Cross-site scripting
- Open redirect
- 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
- 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_uriallows cleartext protocols: if the
redirect_urldoes not properly require
https, the OAuth flow is opened to exploitation in the case of a MITM attack.
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.
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.
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:
- In requests, stored in the browser history
- In transit, accessible to a client who is proxying traffic
- 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.
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.
Thank you to Victor Magierski for assistance in editing this post!
- https://link.springer.com/book/10.1007/978-3-030-36938-5 (OVERSCAN)