1

Recently I was reading about CSRF prevention techniques like Synchronizer Token, Cookie-to-header, and Double Submit Cookie. Cookie-to-header is good for websites using a lot of JavaScript, e.g. SPAs, and Double Submit Cookie eliminates the need to store the CSRF token on the server.

Now, I think I can achieve the best of both worlds with this approach:

  • User logs in.

  • Server generates session ID and CSRF token.

  • Server stores session ID in cookies and makes it http-only.

  • Server signs CSRF token and stores it in cookies.

  • Client uses JavaScript to access CSRF token and sends it on every request via AJAX or Fetch via custom header.

  • Server receives request and checks if CSRF token in cookies is equal to CSRF token in custom header.

  • If they're equal, user is authenticated. And, of course, session ID is automatically sent.

This is very similar to Double Submit Cookie, the difference is that the server doesn't inject the CSRF token in the HTML or anything like that.

I think this approach has many advantages:

  • Can be used in SPAs and web apps made with React, Vue, etc..

  • No need to store CSRF token in the server.

  • Easy to implement.

I also think it's very secure, because:

  • Attacker cannot, by any means, change the contents of the CSRF cookie because it's signed. Note that the CSRF cookie is not http-only, so it can be accessed by JavaScript in the client. However, it's also signed, so it's essentially read-only.

  • Custom headers in requests makes the requests subject to CORS and SOP.

My question is: Is this method secure? I'm not asking for opinions or best practices, I'm asking for objective reasons to use/avoid this approach. What are the pros and cons of this approach? What do you think?

2
  • 3
    "Custom headers in requests makes the requests subject to CORS and SOP." - the requirement of a custom headers in a request is already sufficient to prevent CSRF (assuming no browser bugs), no need to add all the other complicated stuff - see security.stackexchange.com/questions/23371/… Commented Jun 18 at 19:29
  • 1
    You should also make that non httponly cookie same-site strict (might as well, could make it more secure)... only downside is it ties to a session but not a particular getting of form data. When you rotate keys on each request to encrypt the antiforgery token you can send that non httponly cookie on every GET which has the side-effect of guarding against CRIME/BREACH attacks. Each time the antiforgery token is generated a new key is used. I needed to add anti-CSRF because negotiate authenticates and FormData() object used for certain Ajax calls (uploads). Commented Jun 18 at 20:51

2 Answers 2

2

This is, in fact, mostly already the standard way to use double-submit cookies in script-driven apps. The main innovation is signing the anti-CSRF token, which actually doesn't add anything in most cases.

Now, for the actual security of your design. The most obvious potential flaw that I see is that, while you don't say anything about how the anti-CSRF token is generated, it's not obviously tied to the session. Using double-submit cookie or any other secret-token-based CSRF prevention is much weaker if the token is not tied to the session. For example, in your scheme,

"Attacker cannot, by any means, change the contents of the CSRF cookie because it's signed"

is unfortunately false (or at least, not enough information is given to prove it); an attacker who has the ability to edit/plant cookies can simply log into the site themselves, retrieve their own signed anti-CSRF token, plant that in the victim's browser (overwriting any that they currently have), and then submit requests with that token in the request (assuming not blocked by SOP or similar).

Requiring that the attacker have the ability to edit/plant cookies does raise the bar significantly (especially if you use HSTS, and one of the cookie name prefixes that explicitly exist to combat cookie-planting attacks), but fundamentally, the signing isn't doing anything that using a double-submit cookie doesn't already do; if the token isn't tied to the session then an attacker can (one must assume) obtain their own valid anti-CSRF token on demand whether or not it's signed, and if it is tied to the session then you don't need the cookie at all - just stash it in LocalStorage or similar - and the signing doesn't matter because there's presumably only one valid value for that session.

Once you have tied the anti-CSRF token to the session (or at least the user), it's fine to use a double-submit cookie to avoid storing (and looking up) the token on the server. However, there's a potentially more-elegant option at the bottom of this answer.


I do want to mention that this is technically overkill. While there's nothing inherently wrong with using stronger security than is needed, your implied site design already has two different anti-CSRF protections built into it, and adding a third is probably not necessary and might not be worth the increased complexity and attack surface. The two in question are:

  • Adding any kind of secret via script rather than via cookie is inherently CSRF-proof, because the attacker doesn't know the secret. The secret can be something that is also sent in a cookie - this is how double-submit cookie pattern works normally, whether the anti-CSRF token is injected by script or present in a <input type="hidden"> form element - but it could also be stored server-side (requires state, but you loosely implied that you store session tokens statefully anyhow), a signed value such as a JWT (typically stateless, but can be tied to a stateful value such as the session token), or even just a hash (not really any need to make it a keyed hash though you can if you want) of another secret such as the session token. The only requirements are that the secret is not predictable and is tied to the specific user (and ideally to their specific session).
  • Adding any kind of header - or indeed requiring the use of most content types at the server - is inherently proof against CSRF, unless you deeply misconfigure CORS. By default, cross-origin requests that use any custom headers, or in general set any header to any but a small number of allowed values, are disallowed. CORS can be used to allow them on an origin-by-origin basis, and obviously you should not allow origins that are not, in fact, your own sites on which this app is supposed to run, or at least similarly trusted. Unless you go out of your way to thus misconfigure CORS - which will also severely impact site security in other ways - you generally just don't need to worry about it. Requiring (meaning you enforce its presence at the server) a custom header of CSRF: no is perfectly valid CSRF protection, even though it contains no secrets at all, because "cross-origin requests can't set unapproved headers" is part of the same-origin policy (SOP) that is fundamental to webapp security.

If you do want to go with an anti-CSRF-token type of approach, may I suggest a more elegant one? You already have a secret value that the server either knows or can verify statelessly (the session token). Simply hash it with any secure hash function such as a member of the SHA2 or SHA3 families (you can use an HMAC or other keyed hash if you want, but there's no need; if the attacker has access to the victim's session token to generate the hash then it's game over anyhow), send that hashed value to the client (e.g. in an HTTPS response) such that the client stores it in a variable/local storage, and then on every state-changing request from the client, re-hash the session token (after verifying that it's valid) to verify the validity of the presented anti-CSRF token (which may have been transmitted in a header, or the body).

Note that this approach does require that the session token not change unexpectedly. With a random opaque token this is usually true - the token changes only in response to events that would be a normal time for an anti-CSRF token to also change, such as logging in - but stateless tokens (such as the typical use of JWTs) usually change much more often (as protection against misusing a compromised or outdated token, given the inherent difficulty of revoking a stateless token). If your site uses an automatically-rotating session token, you should base the anti-CSRF token on a part that does not change (this could be a random string of sufficient entropy inserted into an arbitrary claim in a JWT, a non-rotating refresh token although ideally those also rotate, or similar).

0

The approach is valid and provides solid security. Combining two different anti-CSRF mechanisms makes sense for defense-in-depth. Even though a custom header should be sufficient (as Steffen Ulrich correctly pointed out), a random token can guard against bugs in browsers or browser-like applications which allow an attacker to set a custom header without triggering a CORS preflight.

Of course the downside is that the approach is fairly complex and only works with JavaScript enabled.

9
  • "a random token can guard against bugs in browsers" - The OP doesn't say anything about any random token. 1) Please explain what you mean by "a random token". 2) Statement "can guard against bugs in browsers" is very broad. Against what bugs? Give 2-3 examples of bugs, when the solution can provide CSRF despite these bugs.
    – mentallurg
    Commented Jun 19 at 0:42
  • 1
    @mentallurg: If you don't know what a random token is, google for it. There are plenty of beginner-friendly resources on the Internet which explain the basics of IT security.
    – Ja1024
    Commented Jun 19 at 3:17
  • The OP doesn't say anything about any random token. That's why the part of the answer "a random token can guard" is not relevant.
    – mentallurg
    Commented Jun 20 at 2:25
  • Statement "can guard against bugs in browsers" is very broad. Against what bugs? Give 2-3 examples of bugs, when the solution can provide CSRF despite these bugs.
    – mentallurg
    Commented Jun 20 at 2:26
  • 1
    Have you even read the OP's question? Step 2: “Server generates session ID and CSRF token.” So which part of this do you not understand? Yes, CSRF tokens in the Double Submit Pattern which the OP’s idea is based on are random. This why they have to be generated. Do you think the OP can just pick the number 4 like in that old xkcd joke? Or are you going to tell us that CSRF tokens have to predictable like you did with salts? At this point, it’s clear you’re either trolling or hopelessly confused.
    – Ja1024
    Commented Jun 20 at 3:16

You must log in to answer this question.

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