10

I've seen several implentations of CSRF tokens:

  • The first one uses randomly generated CSRF tokens which uses a cryptographic strong random generator to generate the token.
  • The second implementation I found uses HMAC which encrypts the session id with secret key stored in the server side config.
  • The third implentation I saw uses a combination of both, a secret key stored in the server side config is used to HMAC a random generated value

The second one does not require state to be stored server side (which is ideal in case of clustering).

I'm wondering what the benefit is between the first and third implementation.

11
  • An unkeyed hash of the session token should work as well. Feb 25, 2014 at 16:05
  • I can't even see an attack against using the session token itself. Feb 25, 2014 at 16:06
  • If the session token is in the markup somewhere, then XSS can trivially access it.
    – Slicedpan
    Feb 25, 2014 at 16:27
  • @Slicedpan the session token isn't present in a readable for, but HMACed so that wouldn't be an issue. Feb 25, 2014 at 16:30
  • It doesn't need to be, if someone can access the markup, they can send that token elsewhere and use it to authenticate as the user, or am I missing something here?
    – Slicedpan
    Feb 25, 2014 at 16:34

3 Answers 3

8

The first one uses randomly generated CSRF tokens which uses a cryptographic strong random generator to generate the token.

This is ideal. In this case, the token is an absolutely unpredictable opaque block with no significance outside its intended context.

The second implementation I found uses HMAC which encrypts the session id with secret key stored in the server side config.

This is simpler to implement. Presumably session IDs are already being generated, so nothing additional needs to be persisted. Since no further storage is necessary, it could be that this mechanism may be implementable in situations where a random token couldn't be worked in to a legacy design. This is a relatively elegant workaround. Assuming the secret key doesn't become known to the attacker, the token shouldn't be reproducible outside the server.

The third implentation I saw uses a combination of both, a secret key stored in the server side config is used to HMAC a random generated value

This is a little silly. Presumably driven by the fact that a an HMAC is a security-related concept, the author attempts to add security to his design by including something security-related. A sort of magic security talisman. Presumably it's no worse than using the random number alone, but it's certainly no better. Unless you count the warm fuzzy feeling you get from using HMAC in your security implementation.

It is, however, a reasonable work-around if you have a poor RNG, though in that case I would probably mix the session ID or something similar in there before hashing so that you're guaranteed that the number you hash in unique.

4
  • 1
    I disagree that randomness is the ideal. A better option is to have a HMAC of some useful information, like username and timestamp (OWASP also suggests a random nonce and the current application action), with a secret key on the server. That way, you have an unforgeable token without requiring server-side state, plus since you have the timestamp, you can easily refresh and/or reject old tokens.
    – ThrawnCA
    Apr 4, 2019 at 6:11
  • This is a good answer but there is a valid reason for using a secret to sign or encrypt a CSRF token in a cookie with a MAC or HMAC as it can protect against bad actors who have control of a subdomain so that they can't place their own CSRF token in cookie (and use that to bypass CSRF controls). Nov 3, 2020 at 11:48
  • @tylerl, what if you don't have a session ID? Then your only option is to set CSRF token as "randomString + hmac(secret, randomString)". That way you can verify CSRF token on the server without any additional user info? Am I missing something here?
    – eddyP23
    Dec 8, 2021 at 16:35
  • @eddyP23 CSRF tokens have to be unforgeable by someone who doesn't have context which usually means something like "wasn't sent the previous page" or something. For any given what if scenario, if the answer isn't obvious then you've got to figure out what "context" actually means in your case -- what you're actually guarding.
    – tylerl
    Jan 13, 2022 at 14:53
-1

I know that asp.net MVC 3/4 uses a method similar to 2. The problem I find with that approach is that there is one CSRF token for the session, as opposed to by form. So for example, if I obtain the CSRF token once, that token is valid for any form post during the course of the session. When testing with a tool such as ZAP, one request gives me the token which I can then reuse at my pleasure. Further to this, the token has to be written somewhere to the HTML page (it is not stored in a cookie, for example) and then could be easily read by an attacker looking for the specific string.

I like the idea of 1/3. The randomness provides security on a by form/call basis, as opposed to one per session. It can also be used to prevent multiple posts of the same form. This would be useful in a transactional system where you would want to ensure that a submit only occurs once. When the server receives the first submit, it invalidates the token. The next submit would fail. The attacker now has to get a hold of a token and use it before the user does.

I can see your point regarding clustering / load balancing, but most load balancers can/will forward requests from the same session to the same backend host.

4
  • 1
    You don't need a token per form, only per user session. If an attacker gets hold of this token all bets are off anyway - your system has already been compromised. Feb 25, 2014 at 20:50
  • Sorry, disagree. See owasp.org/index.php/… Specifically: "To further enhance the security of this proposed design, consider randomizing the CSRF token parameter name and or value for each request. Implementing this approach results in the generation of per-request tokens as opposed to per-session tokens"
    – CtrlDot
    Feb 25, 2014 at 20:58
  • This approach has to be designed into the system from the ground up. You can't just drop in a per form token and expect extra security - the attacker would just execute another GET request and get the new token. This would have to be implemented where each request (even navigation) sends a POST request and any attempt to use the back button logs the user out. Only then you can regenerate the token for each request (as each request is then a POST that can only be generated from the previous POST). Some banking applications employ this approach. Feb 25, 2014 at 21:44
  • @SilverlightFox I agree with that, it has to be designed up front and there are cons to the approach.
    – CtrlDot
    Feb 25, 2014 at 22:18
-1

For the properly stateless systems, you can do the following. Generate a randomString and hmac it with a secret. So your CSRF token is something like this*:

token = "{timestamp} {randomString} {hmac(secret, timestamp + randomstring)}"

Now on the server side you extract timestamp and randomString from the token, hmac them with a secret and verify that it was produced (signed) by the server that holds the secret.

So I believe that Option 3 is the only truly stateless option.

In case you have a session ID, then you can go with Option 2.

*timestamp added to the token and hmac for the purpose of verifying a token's age.

UPDATE!

This seems to be pointless. A hacker could just visit the website, get a legitimate hmaced token and use it. So this is no better than a randomly generated token (just looks fancier). So for hmaced tokens, you need some user identifiable information like sessionId or userId.

OWASP also recommends it:

Add a hash (session id, function name, server-side secret) to all forms.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .