OAuth2.0 Server stack how to use state to prevent CSRF? for draft2.0 v20
Asked Answered
K

3

14

I am using PHP library for OAuth2.0 v20

In draft20, there is a mention of the use of state to prevent CSRF

So far, my own web app that implements this PHP library allows the following:

  1. 3 legged authentication using Authorization Code Request
  2. 2 legged authentication using Resource Owner Credentials Grant
  3. a Request that refreshes an access token

Do I need to use state for all of the 3 situations above?

If so, what is a good example of "state"?

what makes a good "state"?

Any ideal length? Any minimum length? Any maximum length?

Any ideal makeup? alphanumeric including upper case?

Kirwin answered 17/6, 2012 at 13:4 Comment(0)
D
18

Just for #1 -- 3-legged authorization using Authorization Code flow.

When your application exchanges the authorization code for an access token, you want to be sure that the OAuth flow which resulted in the authorization code provided was actually initiated by the legitimate user. So, before the client application kicks off the OAuth flow by redirecting the user to the provider, the client application creates a random state value and typically store it in a server-side session. Then, as the user completes the OAuth flow, you check to make sure state value matches the value stored in the user's server-side session-- as that indicates the user had initiated the OAuth flow.

A state value should typically be a pseudo-random unguessable value. A simple value can be generated as an int with the rand() function in PHP, though you could get more complex as well to provide greater assurance.

The state exists to prevent things like me sending you a link via e-mail which contains an authorization code for my account, you clicking on it and the application pushing all the data into my account unbeknownst to you.

Some additional information is in the OAuth 2.0 threat model document: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-threatmodel-00

In particular, see the section on CSRF protection: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-10.12

Downstream answered 18/6, 2012 at 4:38 Comment(12)
you create a random state value and typically store it in a server-side session. Who is this "you"? the client app? the OAuth authorization Server?Kirwin
what should be the ideal length of the state value? Any minimum length? or maximum length?Kirwin
The client app creates it and verifies it. The OAuth authorization server does nothing more than pass it through.Downstream
Re length max length would be determined by the OAuth provider. I'd use something like a 10 character random string. You're just trying to generate something long enough that it's unguessable.Downstream
Sorry. I am still not entirely sure. So if the client app generates a random 10 character string as state value and passes it in the authorization request along with response_type(set to "code"), client_id(required), redirect_uri, scope(optional), the authorization server will then store this value in server-side session and send back a authorization response including auth code and state. So if the client app receives a non-matching state value, it should attempt to do the authorization again? or ? if it is matching, we continue with the process and state is no longer used.Kirwin
length max length would be determined by the OAuth provider. so in other words, this is determined by me, the architect behind the OAuth Server stack?Kirwin
I also don't quite see how this state prevents CSRF even after reading the docs. I know to prevent CSRF in web forms, server side hashes the HTML fields expected into a string that is now included as a hidden input. So when someone tries to use CSRF and send in a fraudulent request, without the correct hash string, the request is blackholed. This I get. but a random 10 character state value preventing CSRF I actually don't get. Thank you.Kirwin
Sorry- I didn't get that you were the OAuth provider. Yes, you would determine the max length... but be very liberal as this length isn't specified in the spec and clients may do different things.Downstream
let us continue this discussion in chatDownstream
This answer has answered my question, but I have lots of tiny details to ask. Ryan has kindly extended this to a chatroom discussion. See above. Thank you, Ryan for your generosity and sharing.Kirwin
downvoted: don't use rand(). that's not designed to be used for security purposes.Unaccomplished
@Unaccomplished thanks for the comment, though not sure i agree. there's a big difference in trusting something for cryptography (especially when it could be used to determine a key, or encrypting a data for long-term storage) than there is for generating a state value. i don't doubt that other solutions are slightly better, but the risk characteristics of rand in this case don't seem large.Downstream
I
40

It might be helpful to step through an example CSRF exploit in order to understand how a state parameter mitigates such an attack. In this example Mallory is the attacker and Alice is the victim.

The Attack

  1. Mallory visits some client's website and starts the process of authorizing that client to access some service provider using OAuth

  2. The client asks the service provider for permission to request access on Mallory's behalf, which is granted

  3. Mallory is redirected to the service provider's website, where she would normally enter her username/password in order to authorize access

  4. Instead, Mallory traps/prevents this request and saves its URL

  5. Now, Mallory somehow gets Alice to visit that URL. If Alice is logged-in to the service provider with her own account, then her credentials will be used to issue an authorization code

  6. The authorization code is exchanged for an access token

  7. Now Mallory's account on the client is authorized to access Alice's account on the service provider

So, how do we prevent this using the state parameter?

Prevention

  1. The client should create a value that is somehow based on the original user's account (a hash of the user's session key, for example). It doesn't matter what it is as long as it's unique and generated using some private, unguessable information about the original user.

  2. This value is passed to the service provider in the redirect from step three above

  3. Now, when Mallory gets Alice to visit the saved URL (step five above), that URL includes the state parameter generated with Mallory's session information

  4. The authorization code is issued and sent back to the client in Alice's session along with Mallory's state parameter

  5. The client generates a new state value based on Alice's session information and compares it to the state value that was sent back from the authorization request to the service provider. This value does not match the state parameter on the request, because that state value was generated based on Mallory's session information, so it is rejected.

An attacker should not be able to generate a state value for any specific user and, therefore, tricking a user into visiting their authorization URL has no effect.

Incitement answered 13/5, 2014 at 19:58 Comment(8)
When the callback URL of my application called (due to redirect from the service provider) I look at the session cookie (an ecrypted cookie containing the user's id, actually) and know for which user the access was granted. Why do I need the state parameter in that case?Brooklet
Neatly Explained, Good JOB :)Mcclintock
seems to be good eplained. maybe you can say how mallory give alice the url and how alice gives the authorization code to mallory (her client instance should request the access token). i don't get itDentate
1) Mallory can get Alice to click the link the way anybody clicks a link (e.g. by putting it in an email or behind misleading text on another website); 2) The exchange happens between Mallory's account on the client and the service provider.Incitement
I think the attack described is missing details. The authorization URL that Mallory traps is not specific to her account. Sending this to Alice is not an issue; Alice will grant her account on the client access to her account on the resource provider. The problem happens when Mallory changes the redirect_uri in the URL she trapped to point to her own server (which is allowed by the OAuth2 spec, but not very common). In this case, Mallory's server will receive the auth token for Alice. See hueniverse.com/… for more details.Corbel
@WayneBurkett Is it ok if I create the state using SESSIONID like JSESSIONID and keep it inside a cookiePicofarad
The key to this attack is in step 5 where the client associates Alice's authorization code with Mallory's account. But how does that happen?Adjoint
When the client receives the authorization code from Alice's browser, it would be in Alice's session. And a non-stupid client would correctly associate that authorization code with Alice's account.Adjoint
D
18

Just for #1 -- 3-legged authorization using Authorization Code flow.

When your application exchanges the authorization code for an access token, you want to be sure that the OAuth flow which resulted in the authorization code provided was actually initiated by the legitimate user. So, before the client application kicks off the OAuth flow by redirecting the user to the provider, the client application creates a random state value and typically store it in a server-side session. Then, as the user completes the OAuth flow, you check to make sure state value matches the value stored in the user's server-side session-- as that indicates the user had initiated the OAuth flow.

A state value should typically be a pseudo-random unguessable value. A simple value can be generated as an int with the rand() function in PHP, though you could get more complex as well to provide greater assurance.

The state exists to prevent things like me sending you a link via e-mail which contains an authorization code for my account, you clicking on it and the application pushing all the data into my account unbeknownst to you.

Some additional information is in the OAuth 2.0 threat model document: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-threatmodel-00

In particular, see the section on CSRF protection: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-10.12

Downstream answered 18/6, 2012 at 4:38 Comment(12)
you create a random state value and typically store it in a server-side session. Who is this "you"? the client app? the OAuth authorization Server?Kirwin
what should be the ideal length of the state value? Any minimum length? or maximum length?Kirwin
The client app creates it and verifies it. The OAuth authorization server does nothing more than pass it through.Downstream
Re length max length would be determined by the OAuth provider. I'd use something like a 10 character random string. You're just trying to generate something long enough that it's unguessable.Downstream
Sorry. I am still not entirely sure. So if the client app generates a random 10 character string as state value and passes it in the authorization request along with response_type(set to "code"), client_id(required), redirect_uri, scope(optional), the authorization server will then store this value in server-side session and send back a authorization response including auth code and state. So if the client app receives a non-matching state value, it should attempt to do the authorization again? or ? if it is matching, we continue with the process and state is no longer used.Kirwin
length max length would be determined by the OAuth provider. so in other words, this is determined by me, the architect behind the OAuth Server stack?Kirwin
I also don't quite see how this state prevents CSRF even after reading the docs. I know to prevent CSRF in web forms, server side hashes the HTML fields expected into a string that is now included as a hidden input. So when someone tries to use CSRF and send in a fraudulent request, without the correct hash string, the request is blackholed. This I get. but a random 10 character state value preventing CSRF I actually don't get. Thank you.Kirwin
Sorry- I didn't get that you were the OAuth provider. Yes, you would determine the max length... but be very liberal as this length isn't specified in the spec and clients may do different things.Downstream
let us continue this discussion in chatDownstream
This answer has answered my question, but I have lots of tiny details to ask. Ryan has kindly extended this to a chatroom discussion. See above. Thank you, Ryan for your generosity and sharing.Kirwin
downvoted: don't use rand(). that's not designed to be used for security purposes.Unaccomplished
@Unaccomplished thanks for the comment, though not sure i agree. there's a big difference in trusting something for cryptography (especially when it could be used to determine a key, or encrypting a data for long-term storage) than there is for generating a state value. i don't doubt that other solutions are slightly better, but the risk characteristics of rand in this case don't seem large.Downstream
T
-3

As the "state" is just a random string, making something like this should do the trick:

$state = md5(uniqid(rand(), TRUE));

Just remember to save it in your session so you can check it later on.

Tenpenny answered 26/10, 2012 at 17:39 Comment(1)
-1. php.net/manual/en/function.mt-rand.php: "Caution This function does not generate cryptographically secure values, and should not be used for cryptographic purposes. If you need a cryptographically secure value, consider using random_int(), random_bytes(), or openssl_random_pseudo_bytes() instead."Unaccomplished

© 2022 - 2024 — McMap. All rights reserved.