50

https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-30#section-10.12 says:

The client MUST implement CSRF protection [...] typically accomplished by requiring any request sent to the redirection URI endpoint to include a value that binds the request to the user-agent's authenticated state (e.g. a hash of the session cookie [...]

It doesn't say much about the implementation, though. It only puts some light on how the CSRF works:

A CSRF attack against the client's redirection URI allows an attacker to inject their own authorization code or access token, which can result in the client using an access token associated with the attacker's protected resources rather than the victim's (e.g. save the victim's bank account information to a protected resource controlled by the attacker)

But use of the word "rather" rather makes the statement worthless.

I am thinking how to implement the "state" in GAE (using Webapp2). It would be easiest starting at how a hacker could use a CSRF against OAuth2. I found only one good article about the matter: "Cross Site Request Forgery and OAuth2".

Unfortunately, while this blog post is well written, there's not much information beyond explaining the OAuth2. The examples don't work, and I don't know Spring. Still, I found one interesting recommendation there: the server connecting to an OAuth2 provider should store "state" as a random session key (e.g. "this_is_the_random_state":"this_doesn't_matter"), and not a value under a static key (e.g. "state":"random_state_string").


My question is, what's the sane implementation of the "state"?

  • Should the randomly generated state be hashed, or can the same value be stored and sent to the OAuth2 provider?
  • Is there a difference here if the session backend is secure cookies or a server-side storage technology (e.g. in GAE Memcache, or database)?
  • Should state be stored as a key as suggested?
  • Should state has validity period, or is session (if there is one) lifetime enough?
Markus von Broady
  • 686
  • 1
  • 6
  • 14
  • 1
    spring source url given above is broken. This seems to work: http://spring.io/blog/2011/11/30/cross-site-request-forgery-and-oauth2 – user872858 Jun 30 '15 at 10:57
  • This answer looks more precise and clear https://stackoverflow.com/questions/35985551/how-does-csrf-work-without-state-parameter-in-oauth2-0 – nonce Sep 15 '19 at 09:22
  • See also: [security - What is the purpose of the 'state' parameter in OAuth authorization request - Stack Overflow](https://stackoverflow.com/questions/26132066/what-is-the-purpose-of-the-state-parameter-in-oauth-authorization-request) – Peter V. Mørch Mar 08 '21 at 11:42

4 Answers4

51

Let's walk through how this attack works.

The Attack

  1. I visit some client's website and start 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 my behalf, which is granted

  3. I am redirected to the service provider's website, where I would normally enter my username/password in order to authorize access

  4. Instead, I trap/prevent this request and save its URL

  5. I somehow get you to visit that URL instead. If you are logged-in to the service provider with your own account, then your credentials will be used to issue an authorization code

  6. The authorization code is exchanged for an access token

  7. Now my account on the client is authorized to access your 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 information about the original user.

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

  3. Now, I get you to visit the URL I saved (step five above)

  4. The authorization code is issued and sent back to the client in your session along with the state parameter

  5. The client generates a state value based on your 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 my session information, so it is rejected.

Your Questions

  • Should the randomly generated state be hashed or can same value be stored and sent to OAuth2 provider?

The point is that the attacker should not be able to generate a state value that is specific to a given user. It should be unguessable.

  • Is there a difference here, if session back-end is secure-cookies or a server-side storage (in GAE Memcache or database)?

I don't think this matters (if I understand you correctly)

  • Should state be stored as a key as suggested?

I don't know what this means.

  • Should state has validity period, or session (if there is one) lifetime is enough?

Yes, state should have an expiration. It doesn't necessarily have to be tied to the session, but it could be.

Wayne
  • 611
  • 1
  • 5
  • 5
  • From what I read, the state should be generated randomly. You suggest something opposite - to generate it from user data. I also don't understand why the hash should be unique - how do one enforce the uniqueness of a hash? – Markus von Broady May 15 '14 at 16:14
  • It depends on what you mean by random. The client must be able to generate the state for a user and then *independently* re-create that state later, so that it can compare the two. If it's just random, then how would you compare the them? Also, the reason to generate it from user data is that we're trying to *identify* a user. – Wayne May 15 '14 at 16:16
  • I don't recreate it, I store it on the server. – Markus von Broady May 15 '14 at 16:19
  • Why? What are you using the state for? It sounds like you don't even know why you're doing it. The scenario I'm describing *requires* that you re-create the state. That's the whole point. The client receives a state value that only it can create, so it creates it and then compares what it has created to what was passed in. If they match, then this must be the person for which the original state was generated. – Wayne May 15 '14 at 16:21
  • I actually use a framework, that saves the state, but I wondered if it's right, hence the question. I also have read some articles about state, and if I remember well, they all showed an implementation that saved state on server. If the state should be recreated on client and server side both, then the hashing algorithm would be known, so all a hacker would need to get is the data that is hashed. – Markus von Broady May 15 '14 at 16:37
  • *"If the state should be recreated on client and server side both" ... You've misunderstood. Only the client has or can create the state. You're right that the client could store the state for that user. Or they can recreate it. I didn't mean to imply that there was any difference there. The point is that *only the client* knows the correct state for the user that initiated the handshake, so they can be sure it's the same user who has provided authorization. – Wayne May 15 '14 at 16:42
  • If only the client has to (re)create the state, how the server knows it's right? – Markus von Broady May 15 '14 at 16:46
  • When you say server, do you mean the service provider? They do not need to know it's right. They just pass it along. The state exists to prevent a CSRF attack against the client. – Wayne May 15 '14 at 16:48
  • When I say server I mean my server, did we fail to communicate? I thought it's clear what server I mean as I said I store the state on it. – Markus von Broady May 15 '14 at 16:55
  • Yeah, so, when I say **client** I don't mean it as in client/server, I mean it as one of the actors in an OAuth flow. There is a client, a user, and a service provider. Terminology is important. The client is who needs to know how to create and store the user's state. – Wayne May 15 '14 at 16:58
  • let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/14538/discussion-between-lwburk-and-markus-von-broady) – Wayne May 15 '14 at 16:59
  • Link to the resulting chat: http://chat.stackexchange.com/transcript/14538/2014/5/15 – joelittlejohn Aug 14 '14 at 13:06
  • Thanks for this walkthrough--none of the other answers make the threat model easy for me to understand. – chrylis -cautiouslyoptimistic- Jun 12 '17 at 07:40
  • Fantastic answer that even _I_ understand now. – Sam R. Oct 11 '17 at 18:39
  • 8
    Correct me if I'm wrong, but the attack description makes a big assumption that the service provider URL used by the victim contains state that would link the redirect to your own account on the client. Most OAuth client implementations I've seen would not trust this state. – emagdne Jan 28 '19 at 16:28
  • 1) "service provider" is not a standard role according to the OAuth2 RFC section 1.1 2) maybe I misunderstand something because of 1), but I don't think the attacker passes the URL of the interface where she would normally enter her credentials. Instead the post-auth callback URL to the client is trapped and relayed to the victim, thus the authorization code is associated with the logged in *client* session. – buherator Mar 26 '20 at 10:22
  • This is a _possible_ attack (which, as @emagdne points out relies on an unsafe client implementation), but it is _not the only_ attack CSRF protects against. – Alice Heaton Apr 26 '21 at 14:52
  • **wrong!** In step5 - when I click your url, I'm on **my browser**, authorization code used to exchange the token. But how is this token linked to your account since on my browser only I could have been logged in to client. So the token will be linked to my account only not yours. **Then how will your account on client is authorized to access my account on resource server?** Does not make any sense. – VVK Mar 16 '22 at 19:09
  • @VVK See emagdne's comment above. The assumption here is that the URL I got you to click contains state that on the server gets linked to *my account*. That's how the attack works. Of course you're viewing the link in your browser. Nobody is confused about that. – Wayne Mar 22 '22 at 18:43
20

I will simplify this problem. Cross-Site Request Forgery and Clikjacking attacks are useful because it can force a victim's browser into performing actions against their will.

The mention of 10.12. Cross-Site Request Forgery and 10.13. Clickjacking in the OAuth v2 RFC have fundamentally the same concern. If an attacker can force a victim's browser into authenticating, then it is a useful step in forcing the victim's browser into performing other actions.

   in a clickjacking attack, an attacker registers a legitimate client
   and then constructs a malicious site in which it loads the
   authorization server's authorization endpoint web page in a
   transparent iframe overlaid on top of a set of dummy buttons, which
   are carefully constructed to be placed directly under important
   buttons on the authorization page.  When an end-user clicks a
   misleading visible button, the end-user is actually clicking an
   invisible button on the authorization page (such as an "Authorize"
   button).  This allows an attacker to trick a resource owner into
   granting its client access without their knowledge.

Source: 10.13. Clickjacking

For example, Stack Overflow uses OAuth and is vulnerable to this attack. If you visit StackOverflow and you are currently logged into your OAuth provider, you will be automatically logged in to StackOverflow. Therefor an attacker could automatically log in a victim by loading Stack Oveflow within an iframe. If Stack Overflow also had a CSRF vulnerability (and it has had them!), then an attacker could automatically authenticate a victim's browser and carry out a CSRF (Session Riding), Clickjacking, or XSS attack against stackoverflow.com in a Chained Attack.

rook
  • 46,916
  • 10
  • 92
  • 181
  • Thanks. I've done such XSRF (CSRF) attacks (e.g. I sent a link to my friend that made him delete a post on his forum), and I imagine an attack where someone gives you a login URL to a site, where you login as him (victim logs in on attacker's account). It's quite easy to protect against such attack (store state in session), but aren't there more complex hacking techniques than that? – Markus von Broady Oct 02 '12 at 17:31
  • @Markus von Broady Clickjacking, Chaining and XSS is really as complex as attacks get for this RFC violation. There is really no other point in establishing a session for a victim unless you plan on riding on it with other attacks. If you had an XSS vulnerability in SO, this would be a pretty clean account hijack, which is nasty. – rook Oct 02 '12 at 17:56
  • 8
    I do not completely agree with the explanation given by @rook about the potential impact of a successful CSRF attack against the Oauth redirection URI. The point is that an attacker can trick a client (which is running on behalf of the victim) into acquiring an access token that is linked to the attacker (and not to the victim). The impact is that a victim potentially saves sensitive data using an access token that is linked to the attacker: the attacker can then easily access the victim's data at the client by logging in to the external identity provider (source: oauth threatmodel). – Michael Jan 29 '16 at 16:28
5

Taken from The Importance of the state parameter in OAuth2:

This is where the "state" object in OAuth 2 comes into play. By always submitting a non-­guessable state when POSTing to the authorization endpoint, the client application can be certain that the Access Code obtained from the Authorization Server are in response to requests made by it rather than some other client application.

Example:

https://example.com/as/authorization?client_id=client1&response_type=code&scope=openid &state=7tvPJiv8StrAqo9IQE9xsJaDso4

For the state parameter to be useful in preventing CSRF attacks like this, all requests made to the OAuth server must include a state parameter that the client can use to authenticate itself. When sending a state parameter, the OAuth spec stipulates that the Authorization Server must return it to the client verbatim. This will be done by tacking it onto the client's call-back URL. The client must receive this state and be programmed to only accept redirects with a verifiable state. If this is a dictionary kept in memory or a re­-computable value is up to the client programmer.

When deciding how to implement this, one suggestion is to use a private key together with some easily verifiable variables, for example, client ID and a session cookie, to compute a hashed value. This will result in a byte value that will be infeasibility difficult to guess without the private key. After computing such an HMAC, base-64 encode it and pass it to the OAuth server as state parameter. Another suggestion is to hash the current date and time. This requires your application to save the time of transmission in order to verify it or to allow a sliding period of validity (e.g., using TOTP).

Hers is a simple Python example that uses the second suggestion using datetime:

def generate_state_parameter(client_id, private_key):
    date = datetime.datetime.today()
    raw_state = str(date) + client_id
    hashed = hmac.new(private_key, raw_state, sha1) state = base64.b64encode(hashed.digest())
    return (state, date)
Aydin K.
  • 151
  • 1
  • 4
0

In addition to @wayne's answer, here is a different possible attack (that could have been prevented with CSRF):

  1. Attacker gets an OAuth authorization code for their own account ;
  2. Attacker tricks user into following redirection link, with the attacker's authorization code ;
  3. User gets logged in to the application, thinking it's their own account when it is actually the attacker's account.

If the application in question displayed, for example, the bank details of an account where money should be transfered to, then that can be a major problem.

CSRF would have prevented this, as the CSRF code would have been associated with the attacker's session, and not the user's. So the application would have refused the authentication from the user, as the CSRF code would not match the user's session.

Alice Heaton
  • 111
  • 2
  • CSRF is an *attack*, not a *security measure*, so the phrase "could have been prevented with CSRF" is a bit puzzling to me here. In most contexts I'd infer you meant "could have been prevented with [by using CSRF tokens]", but in the context of CSRF attacks against OAuth I'm not really sure what you mean. – Mark Amery Feb 09 '22 at 12:19