Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: IDToken nonce should not be checked (PS-385) #3994

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

splaunov
Copy link
Contributor

@splaunov splaunov commented Jul 11, 2024

This PR deletes code which checks nonce in passed OIDC IDToken against another string passed in the same request.
Replay protection cannot be based on comparing two strings taken from the same request.

Related issue(s)

Checklist

  • I have read the contributing guidelines.
  • I have referenced an issue containing the design document if my change
    introduces a new feature.
  • I am following the
    contributing code guidelines.
  • I have read the security policy.
  • I confirm that this pull request does not address a security
    vulnerability. If this pull request addresses a security vulnerability, I
    confirm that I got the approval (please contact
    [email protected]) from the maintainers to push
    the changes.
  • I have added tests that prove my fix is effective or that my feature
    works.
  • I have added or changed the documentation.

Further Comments

@jonas-jonas
Copy link
Member

Not sure I understand the reason here.
The nonce that is compared to the nonce value from the request, comes from the JWT, which is cryptographically signed, by the OIDC provider, right? So the nonce is not really coming from the same request.

What issue does this solve?

Copy link

codecov bot commented Jul 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 78.12%. Comparing base (1a70648) to head (42c5270).
Report is 77 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3994      +/-   ##
==========================================
+ Coverage   78.10%   78.12%   +0.01%     
==========================================
  Files         362      362              
  Lines       25329    25313      -16     
==========================================
- Hits        19783    19775       -8     
+ Misses       4028     4022       -6     
+ Partials     1518     1516       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@splaunov
Copy link
Contributor Author

Hi Jonas!
Here are some issues that come to mind:

  • a false sense of security. We might be thinking that we are protected from replay attacks but we are not
  • unnecessary lines of code on both client and server side. There is no other way for calling party to send the nonce but just extract it from idtoken and copy into the id_token_nonce

Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two nonces:

  • The nonce we keep in our application and send as verification to Ory Kratos
  • The nonce the upstream server send in the ID token

Since the second nonce (the user-provided value) is in a signed ID token, and the first nonce comes (most likely) from you initializing the OIDC call, the verification very much makes sense.

@splaunov
Copy link
Contributor Author

Nonce checking only makes sense for party which generates the nonce.
On the kratos side we cannot trust the value in id_token_nonce field. As we do not know if it was just copied from the token itself or really generated before receiving the token.

And the client libs out there do not necessarily provide nonce value that was originally generated. Client-side code can take it only from the received idtoken.
As an example: https://pub.dev/documentation/google_sign_in/latest/google_sign_in/GoogleSignInAuthentication-class.html

@aeneasr
Copy link
Member

aeneasr commented Jul 11, 2024

By using this endpoint you delegate "Nonce checking only makes sense for party which generates the nonce." to Ory Kratos. Therefore, in my view, it is totally legitimate for Ory Kratos to do the heavy lifting of giving it's client the ability to verify the nonce it generated against the verified claims of the ID token.

If no nonce is availalbe, omit it and it will be ignored

@splaunov
Copy link
Contributor Author

splaunov commented Jul 11, 2024

If no nonce is availalbe, omit it and it will be ignored

If no generated nonce is available we still need to send in the id_token_nonce field a copy of the value taken from idtoken.
Otherwise kratos generates error.

@aeneasr
Copy link
Member

aeneasr commented Jul 11, 2024

The nonce in the ID Token is only set if the OIDC request included a nonce (/oauth2/auth?client_id=...&nonce=...), otherwise the field is empty. If you did supply a nonce, then yes, it needs to be verified by the server.

@aeneasr
Copy link
Member

aeneasr commented Jul 17, 2024

We're looking into a solution for this now!

@aeneasr
Copy link
Member

aeneasr commented Jul 26, 2024

After careful review, it appears that some SDKs do not correctly handle the nonce (don't return it) or even return the same token multiple times. So I think this PR is valid, we'll just have to see if we disable it completely or on a provider-per-provider basis.

@aeneasr aeneasr closed this Jul 26, 2024
@aeneasr aeneasr reopened this Jul 26, 2024
@wonrax
Copy link

wonrax commented Aug 18, 2024

We have exchanged emails in april with the same vulnerability description but it was impossible to make you consider this important so I gave up. Simply removing the nonce check does not address the elephant in the room. The nonce is still useless as it is right now, and @splaunov is absolutely right: it gives "a false sense of security. We might be thinking that we are protected from replay attacks but we are not".

If you are confident enough to assume that your users' ID tokens will never get leaked (by any mean such as accidental server-side logging, local network breach, etc.) I still think it's better just to drop the nonce support altogether, to reduce cognitive and maintenance burden.

Below are the emails I sent.

In the login with OIDC flow, the nonce is checked in the update registration flow step [1], however, the nonce value is retrieved from the request payload [2] instead of server side storage (e.g. database or in-memory). This imposes a possible replay attack where the attacker captures and retransmits the legitimate ID Token in the POST request. Correct nonce implementations such as invalidating nonce after the first use or associating the nonce with an identifier (e.g. hashing your flow ID and using it as the nonce value) can be used to prevent this from happening.

Also, in the OpenID Connect Basic Client Implementer's Guide 1.0 - draft 47 [3], under Section 2.2.1. ID Token Validation there states:
The iat Claim can be used to reject tokens that were issued too far away from the current time, limiting the amount of time that nonces need to be stored to prevent attacks. The acceptable range is Client specific.

[1] https://github.com/ory/kratos/blob/v1.1.0/selfservice/strategy/oidc/strategy.go#L700
[2] https://github.com/ory/kratos/blob/v1.1.0/selfservice/strategy/oidc/strategy_registration.go#L156
[3] https://openid.net/specs/openid-connect-basic-1_0.html

I believe you were referring to the OIDC implicit flow, which uses the Id Token scope including Apple Google Sign In on iOS [1] and web [2], Google Sign In on Android [3] and web [4], etc. In contrast to the code flow in which the authorization providers will allow the code to be used only once (to exchange for access tokens), this flow does not require direct communication between the authorization server and the relying client party because the validation of Id Tokens is performed using the public keys provided by these services. Consequently, it is theoretically possible for an attacker to initiate a flow and exploit a leaked Id Token from a legitimate user to complete the authentication process.

This is where the importance of a nonce comes into play. By definition, nonce is "a value that is used only once" [5], ensuring that an Id Token is invalidated after its initial use. However, based on my shallow reading, it appears that your current implementation may not be fully leveraging the nonce to its intended purpose. However, if you decide to incorporate this security measure, I strongly recommend ensuring its correct application to avoid potential confusion and maintenance challenges in the future.

[1] https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple
[2] https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/configuring_your_webpage_for_sign_in_with_apple
[3] https://developer.android.com/training/sign-in/credential-manager
[4] https://developers.google.com/identity/gsi/web/guides/features
[5] https://csrc.nist.gov/glossary/term/nonce

@aeneasr
Copy link
Member

aeneasr commented Aug 27, 2024

Hello all, and thank you for being so patient with us. We have investigated this issue internally for some time now. Initially, we also thought to have discovered a potential ID Token Replay attack, which is why we stopped commenting on this issue as we tracked this internally as a potential CVE.

However, during working on a fix for this, we realized just how broken the native SDK ecosystem is. As @splaunov has correctly pointed out, many libraries do not return the nonce. But not even that, some SDKs go so far as to return the same ID token multiple times when calling auth (apparently a Google SDK is doing this):

After implementing the proposed fix, it was noticed that Google returns the same ID Token for the live time of the token, when calling sign in with Google. This is problematic, because the user wouldn't be able to login again, during the live time of the token.

Apple does not seem to have the same optimization in place (at least for the expo library tested), but we can't be sure that the same optimization is not in place in other libraries.

So generally speaking, it is not easily possible to protect this endpoint against token replay attacks. Hence, I also now agree that the nonce checking doesn't really serve a purpose. The nonce should really be checked by the library doing the auth code exchange.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants