Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-31999: Prevent Attacks and Redirect Users with OAuth 2.0 State Parameters

All versions of @fastify/oauth2 used a statically generated state parameter at startup time and were used across all requests for all users. The purpose of the Oauth2 state parameter is to prevent Cross-Site-Request-Forgery attacks. As such, it should be unique per user and should be connected to the user’s session in some way that will allow the server to validate it.

v7.2.0 changes the default behavior to store the state in a cookie with the http-only and same-site=lax attributes set. The state is now by default generated for every user. Note that this contains a breaking change in the checkStateFunction function, which now accepts the full Request object.

CVE
#csrf#web#js#oauth#auth

Authorization protocols provide a state parameter that allows you to restore the previous state of your application. The state parameter preserves some state objects set by the client in the Authorization request and makes it available to the client in the response.

CSRF attacks

The primary reason for using the state parameter is to mitigate CSRF attacks by using a unique and non-guessable value associated with each authentication request about to be initiated. That value allows you to prevent the attack by confirming that the value coming from the response matches the one you sent.

The state parameter is a string so you can encode any other information in it. You send a random value when starting an authentication request and validate the received value when processing the response. You store something on the client application side (in cookies, session, or localstorage) that allows you to perform the validation. If you receive a response with a state that doesn’t match, you can infer that you may be the target of an attack because this is either a response for an unsolicited request or someone trying to forge the response.

A CSRF attack specifically targets state-changing requests to initiate an action instead of getting user data because the attacker has no way to see the response to the forged request. For the most basic cases the state parameter should be a nonce, used to correlate the request with the response received from the authentication.

Most modern OIDC and OAuth SDKs, including Auth0.js in single-page applications, handle the state generation and validation automatically.

Set and compare state parameter values

  1. Before redirecting a request to the Identity Provider (IdP), have the app generate a random string. For example:

    The allowed length for state is not unlimited. If you get the error 414 Request-URI Too Large, try a smaller value.

  2. Store the string locally. For example:

    storeStateLocally(xyzABC123)
    
  3. Add the state parameter to the request (URL-encoding if necessary). For example:

    // Encode the String   
    tenant.auth0.com/authorize?...&state=xyzABC123
    

    After the request is sent, the user is redirected back to the application by Auth0. The state value will be included in this redirect. Note that depending on the type of connection used, this value might be in the body of the request or in the query string.

    /callback?...&state=xyzABC123
    
  4. Retrieve the returned state value and compare it with the one you stored earlier. If the values match, then approve the authentication response, else deny it.

    // Decode the String
    var decodedString = Base64.decode(encodedString);
    if(receivedState === retrieveStateStoredLocally()) {
     // Authorized request
    } 
    else {
      // This response is not for us, reject it
    }
    

Redirect users

You can use the state parameter to encode an application state that will put the user where they were before the authentication process started. For example, if a user intends to access a protected page in your application, and that action triggers the request to authenticate, you can store that URL to redirect the user back to their intended page after the authentication finishes.

Generate and store a nonce locally (in cookies, session, or local storage) along with any desired state data like the redirect URL. Use the nonce as a state in the protocol message. If the returned state matches the stored nonce, accept the OAuth2 message and fetch the corresponding state data from storage. This is the approach we use in auth0.js.

Use the stored URL to redirect users

  1. Set the nonce state parameter value that you used to mitigate CSRF attacks as explained above.

  2. Store the nonce locally, using it as the key to store all the other application state information such as the URL where the user intended to go. For example:

    {
      "xyzABC123" : {
        redirectUrl: '/protectedResource',
        expiresOn: [...]
      }
    }
    
  3. Authenticate the user, sending the generated nonce as the state.

  4. As part of the callback processing and response validation, verify that the state returned matches the nonce stored locally. If it does, retrieve the rest of the application state (like the redirectUrl).

  5. Once you complete the callback processing, redirect the user to the URL previously stored.

Alternate redirect method

  1. Generate and store a nonce value locally.

  2. Encode any desired state (like the redirect URL) along with the nonce in a protected message (that will need to be encrypted/signed to avoid tampering).

  3. In the response processing, unprotect the message, getting the nonce and other properties stored.

  4. Validate that the included nonce matches what was stored locally and, if so, accept the OAuth2 message.

Limitations and considerations

  • Choose a storage method based on your application type.

    App Type

    Storage Recommendation

    Regular Web App

    Cookie or session

    SPA

    Local browser

    Native App

    Memory or local

  • From a security perspective, neither the request nor the response is integrity-protected so a user can manipulate them. That is true for adding a parameter to the redirect_uri as well.

  • The allowed length for state parameter value is not unlimited. If you get the error 414 Request-URI Too Large, try a smaller value.

  • Passing URLs in plaintext or in any predictable way is unsafe. Ensure that the state parameter value is:

    • Unique and opaque to ensure that it can be used for defense against CSRF and phishing attacks.

    • If stored in a cookie, it should be signed to prevent forgery.

Learn more

  • Which OAuth 2.0 Flow Should I Use?
  • Sessions

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907