The Importance of the state parameter in OAuth2
In this overview, we will look at what CSRF is and why it's important to protect your OAuth client against it.
Cross Site Request Forgery (CSRF) attacks were discovered over 10 years ago, as an alternative to the Cross Site Scripting (XSS) attack. There are not many known attacks, mainly due to it's hidden nature and because it requires an attacker to fly blind. As the number of claims-based systems increases, however, this attack is becoming more relevant. In this overview, we will look at what CSRF is and why it's important to protect your OAuth client against it.
Similarly to the Cross-site Scripting Attack (XSS), CSRF attacks use calls to unintended servers. The attacker inserts an HTML image tag or a link with a request to a resource that the victim owns, but is not currently operating against. The idea is to use the existing sessions available in the browser to perform operations to an authenticated resources. The difference to XSS is that the browser does not block a page in one domain from fetching URLs from another domain. Because of this, XSS is harder to perform today. Browsers can't protect from CSRF in the same way because it is next to impossible for them to distinguish an attack from legitimate linking. For a browser to provide built-in protections, it would have to understand the context of what it is displaying.
A user named Alice logs into her Web mail account. She later visits a completely different site, using the same browser and without logging out of Web mail provider. This other site is operated by an attacker, Eve, who has inserted an image tag containing a URL with an attacking link, e.g., something like this:
<img src="http://alices-email-provider.com/?sendEmailTofirstname.lastname@example.org" />
At this point, Eve has used the session in Alice's browser to trigger an operation on her email account without Alice's consent or knowledge.
This is a very limited type of attack, however, because Eve cannot monitor its progress. All status codes are returned directly to the browser, so Eve is “flying blind.” Another limitation is that Alice must have logged in to the resource under attack before seeing the image link. Also, the resource under attack must be possible to control using a URI API. As a result, the chances of falling victim to CSRF attacks is low.
The advent of claims and tokens
However as more and more sites are moving over to OAuth and token-based authorization, a new type of phishing attack surfaced that used this CSRF technique. There are two main ways of running this attack.
1. Attacking the redirect URI
Instead of using an existing user session, the attacker tries to trick the victim into using the attacker's protected resource rather than it's own. By attacking the redirect URI, the attacker can insert its own Access Code. When the victim submits this code to the OAuth Authorization Server, she will receive an Access Token intended for the attacker. If this misappropriation of Access Tokens is unbeknownst to the victim and the OAuth server, the victim will end up using the attacker's tokens when making requests to the protected API. Consequently, the victim may end up receiving forged or otherwise incorrect data that she may believe to be her own rather than the attacker's.
2. Attacking the authorization endpoint
This attack aims at leading the victim to use the attacker's API rather than the actual one. This is done by authorizing a malicious client to use the victim's protected resource provider without the end user's knowledge or involvement. This must be prevented by protecting the Authorization Server from CSRF attacks. Predefined redirect URIs are a key protection mechanism though other preventative methods are also available.
Example: Redirect URI attack
- Eve publishes an image link on a forum. The src attribute points to Eve's hostile redirect, and is renders as a "broken image" by browsers.
- Alice visits this forum, and the image URL is retrieved.
- Instead of a 200 OK, the response is a 302, redirecting Alice's browser to the known redirect URI of an OAuth Server. In this request, Eve replaces Alice's Access Code with her own. This code has been obtained by Eve after authenticating herself to the Authorization Server.
- This triggers the call-back logic in Alice's application to retrieve the Access Code and convert it into an Access Token.
- Now Alice has an Access Token against Eve's malicious resource rather than her own. This could be something that Alice had running in a different tab of the browser.
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.
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)
If you have any expirence in defining TOTP based states or other ways, drop us a line below in the comments section!
The state parameter is included in the OAuth 2 specification to protect your client application from CSRF attacks. It helps ensure that Authorization Codes requested by one client aren't used by another. Building asynchronous applications results in a highly cohesive system without much decoupling. This introduces injection risks that can be mitigated by using opaque states that only make sense to the originator. By passed them between the client and the server, the client who initiated the request can later authenticate itself. In this way, the client, OAuth server, and their users are protected from attackers. This one-party authentication can easily be implemented using symmetric key encryption, and is a must when building such systems.
For more information on OAuth2 security measures, take a look at section 10 in the OAuth2 RFC6749.