In unserem Blog veröffentlichen wir in unregelmäßigen Abständen Artikel zu verschiedenen Themen der IT-Sicherheit, wie z.B. Open Penetrationstests, öffentlichen Bedrohungsanalysen und Analysen zu anderen interessanten Themen.

“Proof Key for Code Exchange” (also known as PKCE) [1] is a well-known protection mechanism for OAuth and OpenID Connect.

It was initially introduced to protect leaked or stolen authorization codes of benign native and mobile apps (which are public clients) from being redeemed by malicious apps.

With the time PKCE has become one of the standard measures to improve the security of OAuth and OpenID Connect protocol flows for all kinds of clients. The best current practices draft [2], which advises on how to implement OAuth securely, recommends to generally use PKCE to protect the authorization code and also prevent CSRF attacks.

However, PKCE cannot protect your client against all attacks which target the code. In this blog post, we will explain one attack example, which circumvents PKCE and allows an attacker to use a stolen code to access the victim’s resources.

At the beginning, we want to introduce the basic concept of PKCE (see Figure 1):

when the client creates the authorization request, it adds two new parameters; these are code_challenge and code_challenge_method (1). The code_challenge is the hash value of the so-called code_verifier, where SHA-256 is the most frequently used hash algorithm. The code_verifier is randomly generated by the client for each authorization request and kept secret. The code_challenge_method references the hash algorithm used.

Let us summarize:

code_challenge = code_challenge_method(code_verifier) = sha256(code_verifier)

After the end-user gives their consent (2), the authorization server issues a code and binds it to the code_challenge that the authorization server received from the client (3).

The client redeems the code at the authorization server by sending the token request (4). Besides the code itself, this request contains client credentials (or any client authentication method used in the scenario) and the code_verifier.

Before issuing an access_token (5), the authorization server computes the hash value of the code_verifier using the hash algorithm specified by the code_challenge_method. The authorization server then compares it to the code_challenge received in the authorization request. If these hash values match, the authorization server knows that the authorization request and the token request were issued by the same client because the client’s code_verifier was kept secret until the token request. Effectively, PKCE binds the authorization request to the token request. One additional benefit of PKCE is that it can provide protection against Cross-Site Request Forgery (CSRF) attacks: the client must be certain that the authorization server supports PKCE. If this is the case the client cannot redeem a code received in a CSRF attack because it lacks the correct code_verifier. This makes the use of the state parameter as a CSRF protection obsolete.

 

PKCE Flow

PKCE Flow

Figure 1: Example of an OAuth code grant with PKCE.

While we fully agree that PKCE provides a great security increase to OAuth and encourage every customer to always use PKCE when possible, there are some limitations to the protection PKCE can provide. At the OAuth Security Workshop 2020 (held virtually this July), we had an interesting discussion with other OAuth enthusiasts about these limitations and attacks which PKCE cannot prevent.

One example of such an attack is discussed below.

The PKCE Chosen Challenge Attack

A couple of years ago, we published a similar attack concept for native Apps.
In the following, we show that the basic idea can be applied to web applications as well.

Attack Phases

In the so-called “PKCE Chosen Challenge Attack”, an attacker steals a code issued for the victim and is able to access the victim’s resources by abusing a legitimate client. As you will see, PKCE is not able to prevent this attack. The attack is depicted in Figure 2 and consists of three phases:

  • Phase 1: The attacker starts an authorization grant with the legitimate client. The legitimate client constructs an authorization request containing a freshly generated code_challenge and tries to send it to the authorization server by redirecting the attacker’s user-agent (1).
  • Phase 2: The attacker does not execute the redirect but lures the victim into sending the authorization request to the authorization server using their own user-agent (2). There are different ways for an attacker to achieve this, for example, he could send the authorization request as a link via email or place the link on the attacker’s website. The attacker also lures the victim in authenticating at the authorization server and consenting the access (3). In some scenarios, this might happen automatically if the victim is already authenticated at the authorization server. Note that the name of the legitimate client is shown on the consent page. The authorization server issues a code and sends it back to the client by redirecting the victim’s user-agent (4). The client, however, does not have a session with the victim’s user-agent and does not expect an authorization response from this user-agent. Therefore, the client ignores the authorization response and does not try to redeem the code.
  • Phase 3: The attacker then steals this code; examples of how the attacker can achieve this are described below. The attacker can now continue the authorization grant started in phase 1. The attacker sends an authorization response to the client, which contains the state value from the first authorization request (1) and the victim’s code from (5). As the state value matches the expected value and the client is waiting for an authorization response from the attacker’s user-agent, it accepts the response. Afterward, the client sends the code along with the code_verifier and its client credentials to the authorization server (6). The authorization server issues an access_token and sends it back to the client (7) after validating the client credentials and the code_verifier. Finally, the client requests the victim’s resources at the resource server using the access_token (8) and allows the attacker to access them (9).

PKCE’s Effect on the Attack

The presented attack is possible although both the authorization server and client support and make use of PKCE and act as they are supposed to do throughout the whole attack.
Although described for OAuth here, the same attack works flawlessly for OpenID Connect.

PKCE cannot prevent this attack because the attacker forwards the authorization request to the victim unaltered. This makes the victim use the exact same code_challenge in the authorization request the client has initially generated. Therefore, the client includes the correct code_verifier in the token request. which is valid for the code_challenge the authorization server received.

Remember what we defined as the goal of PKCE earlier: the authorization request and the token request are issued by the same client. This goal is not violated in the attack described above. In other words: PKCE is working as expected.

 

PKCE Attack

PKCE Attack

Figure 2: Overview of the PKCE Chosen Challenge Attack.

Attack Requirements

If an attacker wants to execute the attack he has to fulfill three requirements:

  1. The attacker must be able to lure the victim into issuing the authorization request (2).
  2. The attacker must be able to steal the code issued by the authorization server.
  3. The attack must be performed “live”. The attacker’s grant must be started prior to the victim’s grant but it is likely that the client does not wait for the authorization response of the attacker’s grant for a long time. Therefore, the attacker cannot start their grant to “collect” the authorization request long before the actual attack.

For all requirements, there are various ways for the attacker to fulfill them. One example is a malicious extension installed in the victim’s browser. In Google Chrome an extension is able to read, manipulate, and create HTTP requests in the victim’s browser using the following permissions:

 

"permissions": [ "webRequest", "webRequestBlocking", "*://*/*" ]

This allows the attacker to send the parameters from the authorization request from their own authorization grant (1) to the victim’s browser and start the victim’s authorization grant there immediately (2). Additionally, the extension enables the attacker to capture and steal the code when it is returned from the authorization server (3) and paste it to their own grant. All three requirements are successfully fulfilled.

Another way for the attacker to fulfill the first requirement could involve social engineering: After the attacker started their authorization grant they send a link to the victim and lure the victim in clicking on the link which issues the authorization request from the victim’s browser. To steal the code, the client must leak it. Some clients, for example, leak the code in the referer HTTP header of their advertisements or other embedded content to which the attacker might have access.

Conclusion

PKCE was developed to make sure only the same party which requested a code by issuing an authorization request is able to successfully redeem the code using the token request. The PKCE parameters code_verifier and its hash value code_challenge bind the authorization request and the token request together to ensure exactly this goal. In addition, when the client can be sure that the authorization server implements PKCE correctly, PKCE serves as a CSRF protection, as well.

As we stated at the beginning of the blog post, PKCE is a great measure to increase the security of your OAuth or OpenID Connect application but it can only provide protection in the scope it was designed for. Other attacks such as the PKCE chosen challenge attack presented above work around this scope. In the presented attack the same honest client is abused for both the authorization request and the token request. The goal of PKCE is fulfilled because the same honest client issues both requests.

We published another example of an attack which circumvents the scope of PKCE called “App Impersonation” a couple of years ago. In this attack, a malicious application installed on the victim’s device impersonates a public client by using it’s client_id and redirect_uri and imitating its user interface. With the malicious application, the attacker lures the victim into authorizing access to the victim’s data. The attack is described here in more detail.

To conclude we want to emphasize that we strongly believe PKCE serves as a great addition to most OAuth and OpenID Connect implementations. We generally recommend to use it in your application, but be aware of which attacks it protects you against and what it’s limitations are.

Update: PKCE in Comparison to the nonce Parameter in OpenID Connect

After publishing this blog post, a reader asked an interesting question on Twitter:

"Wouldn't you say that 'nonce' offers the same protection in general for non public OIDC clients?"

Ioannis Kakavas (Tweet)

To answer this question, we will compare the nonce parameter which is only available in OpenID Connect (OIDC) to PKCE which can be used in OAuth and OIDC.

First, a short reminder of how the nonce parameter is used in OIDC: The value of the nonce parameter is chosen by the client and included in the authorization request; similarly like the PKCE code_challenge. The authorization server includes the same value of the nonce parameter in ID tokens. When the client uses the response_type value id_token, the authorization server includes an ID token in the authorization response and sends it back to the client alongside with the code. The client validates the ID token and compares the included value for the parameter nonce with the value it initially chose for this authorization grant. If the two values do not match the client aborts the authorization grant.

In this blog post, we discussed the protection PKCE offers and the limitations that its protection has. To sum up, the following three aspects are important for the comparison to the nonce parameter:

  1. PKCE protects your application against CSRF attacks. The client will not be able to provide the correct code_verifier because it is not bound to the session with the victim of the CSRF attack. PKCE binds the authorization request to the token request.
  2. PKCE prevents an attacker from redeeming a stolen code by themselves. The attacker is not able to provide the correct code_verifier for the stolen code.
  3. PKCE does not prevent an attacker from abusing a legitimate client to redeem a stolen code. One example of this attack pattern is the "PKCE Chosen Challenge Attack" described above.

Now let us have a look at the protection the nonce parameter offers for these three aspects:

  1. The nonce parameter also protects your application against CSRF attacks. The client will redeem the code at the authorization server but will recognize that the ID token does not contain the correct value for the nonce parameter. The nonce parameter binds the authorization request to the token response.
  2. The nonce parameter does not prevent an attacker from redeeming a stolen code by themselves. However, the question asked by the reader involves confidential (= non public) clients. This means the authorization server should enforce client authentication before a code is successfully redeemed. The attacker cannot authenticate as the legitimate client and is therefore not able to redeem the stolen code by themselves.
  3. The nonce parameter does not prevent an attacker from abusing a legitimate client to redeem a stolen code. Similarly to the "PKCE Chosen Challenge Attack" the attacker can use two parallel grants to redeem a stolen code. The attacker lures the victim into sending the authorization request from the attacker's grant to the authorization server. The authorization request is created by the client and contains the value for the nonce parameter which was chosen by the client for the grant with the attacker. The IdP will issue an ID token which contains the correct nonce value and the attacker can inject this ID token into his grant.

Comparing these three aspects the protection PKCE offers and the protection offered by the nonce parameter seem to be quite similar indeed. However, there are two important differences between PKCE and the nonce parameter.

The first difference is which party is obliged to validate and enforce the protection mechanism. While PKCE is a mechanism enforced by the authorization server, the validation of the nonce parameter is completely in the responsibility of the client. It depends on your perspective - whether you are operating an authorization server or a client - if PKCE or the nonce parameter is the right measure for you to protect your application.

The second difference between the protection provided by PKCE and by the nonce parameter is the step of the authorization grant in which the protection becomes effective. The authorization server validates PKCE before issuing an ID token and a code, while the client is only able to validate the nonce parameter after the authorization server has already issued the authorization response containing the ID token and the code. Even when the client correctly validates the nonce parameter and aborts the authorization grant, the IdP is not aware of the fact that an attack has occured. If the ID token or the code is stolen an attacker might be able to abuse them for further attacks.

One additional benefit of the nonce parameter is that it prevents replay attacks targeting the ID token.

In general, we highly recommend using both PKCE and the nonce parameter if possible. This enables each party to be certain about the security of the authorization grant without relying on the other party.

If you have further questions about the topic of this blog post, feel free to contact us.

 

If you want to learn more about attacks or the security of PKCE, OAuth, OpenID Connect, and other Single Sign-On solutions, you can also book one of our in-depth Single Sign-On training courses.

Do you think the attack described above is interesting and would you like to execute it in a penetration test yourself? Have a look at our career page and see if there is an interesting job offer for you.

 


[1] N. Sakimura et al. Proof Key for Code Exchange by OAuth Public Clients. RFC 7636 (Proposed Standard). Internet Engineering Task Force, 2015. URL: https://tools.ietf.org/html/rfc7636.

[2] T. Lodderstedt et al. OAuth 2.0 Security Best Current Practice - Draft 15. Internet Engineering Task Force, 2020. URL: https://tools.ietf.org/html/draft-ietf-oauth-security-topics-15.