0

The (anti) CSRF Token should protect user from executing a action on the website by clicking a link or a form that is created by an attacker.

In the application that I want to secure I can't use an existing framework and I can't use html forms everywhere and I can't use Javascript to set headers. The application often uses just simple links and this can not easily be changed.

Still I like to have CSRF protection and want to know if the following approach would be secure. The approach relies only on a strict samesite cookie.

It should work like this:

A) For users without session:

When the application is installed a strong random(256bit) secret is generated (called SHARED_SECRET).

When a user visits the page the first time, a the cookie (with settings samesite=strict, secure, http-only, host-only) with the a strong random (128bit called R) + TIMESTAMP + SIGNATURE is created. I call it CSRF_TOKEN. The SIGNATURE is defined as hmac_sha256(R+TIMESTAMP,SHARED_SECRET). Every page load or ajax request creates a new CSRF_TOKEN and replaces the existing CSRF_TOKEN.

If the user does a action e.g. by pressing a action link the CSRF_TOKEN cookie is read on server side. The value of the CSRF_TOKEN cookie is split into R, TIMESTAMP and SIGNATURE and the SIGNATURE' is calculated again using hmac(R+TIMESTAMP,SHARED_SECRET) the result compared against the SIGNATURE from the CSRF_TOKEN.

If the signatures are equal (with a equal function save against timing attacks) and the timestamp is not older then ~15min and Sec-Fetch-Site is not present or value equals same-origin the action will be executed.

B) For users with session:

The session key is stored in cookie with (samesite=lax, secure, http-only, host-only) and a strong random csrf token is generated and stored in the CSRF_TOKEN cookie (samesite=strict,secure, http-only, host-only) and within the server side session store.

For each action triggered by the user the session is loaded and CSRF_TOKEN token is compared with the value stored in the session and if Sec-Fetch-Site is present it's checked if the value equals same-origin.

I guess with the good browser support for the samesite=strict and Sec-Fetch-Site feature it may be a secure solution but maybe I missing something. Would it be secure CSRF protection?

9
  • welcome - please clarify whether R contains any timestamp data, and if not, whether it could be modified?
    – brynk
    Commented Jun 22, 2022 at 17:01
  • it does not, should I add a timestamp?
    – key_
    Commented Jun 22, 2022 at 18:13
  • i asked because this (or tieing to the session) would likely contribute to a compromise/ partial solution (if one is possible!? security.stackexchange.com/a/225106 CBHacking'20), eg. under your scheme if i can convince your browser to open a link to "delete account id 0", would a valid cookie be sent? there are many existing Q+A's (eg. security.stackexchange.com/a/51229 SilverlightFox'14 describes the prob well) but from what i can tell no recent discussion of stateless operation/ or without modifying the client request to include the token for comparison with the cookie
    – brynk
    Commented Jun 22, 2022 at 18:59
  • 1
    and more: security.stackexchange.com/q/59470 Gili'14 "Double Submit Cookies vulnerabilities" discussing sub-domains and host-only, setting secure flag, https-only server operation, hsts .. plenty of edge cases to consider (be sure to follow the links in the comments as well)
    – brynk
    Commented Jun 22, 2022 at 19:14
  • the token ties the session to the form. You need a hidden field in the form for that to work. Make sure the cookie is set "http-only". That bit is important, too... but why would you set same-site to lax? Creating the session and authenticating the session are separate concerns from anti-csrf tokens. These tokens make it harder to impersonate the web site. (the attacker page can no longer be static) Commented Jun 22, 2022 at 19:21

2 Answers 2

2
+100

This approach is overcomplicted and probably unnecessary (in the no-session case), and possibly insecure (in the session case) anyhow because it ultimately rests on SameSite, which is a defense-in-depth measure rather than a reliable protection.

First of all, you talk about CSRF protection for users without a session, but that almost certainly doesn't make sense. The usual purpose of CSRF is that it's a way for an attacker to take actions in the context of the victim's session. Sometimes it matters anyway if you are authorizing users based on something other than session - e.g. by IP address, location within a LAN/VPN/localhost, HTTPS session, HTTP Authorization (Basic or Digest), or so on - but that's extremely rare and nothing you've said suggests that is happening here. Given that, trying to prevent CSRF for sessionless users is like trying to lock a car door that isn't part of a car. There's nothing for it to secure.

Second, your whole cookie-for-sessionless-users thing is literally just re-inventing JWTs (better to just use existing systems) and then sticking this neo-JWT in a samesite cookie (reasonable thing to do with a JWT) and calling it an anti-CSRF token (it's not). For anti-CSRF purposes, this is no stronger than having a completely empty cookie called "NotCSRF" which has the same flags as the one you're using. The value of the cookie doesn't matter, because you don't have anything to compare it to. Since you don't have any way to distinguish one user from another (unless you're storing the R value on the server and in some way associating it with the user, in which case this is just a weird session token after all, albeit possibly an unauthenticated session), an attacker can trivially get their own (valid) cookie and send requests using that from their own machine (no need for CSRF, as above). In the event CSRF matters, SameSite on the "NotCSRF" cookie prevents the attacker from making CSRF requests anyhow (you just request any request without a cookie called that). If the attacker could get past the weaknesses in cookie-based CSRF protection (by planting a cookie on the victim's browser), the neoJWT complicated version still offers no more security than the blank cookie because an attacker can just go get their own (valid) cookie and plant that instead.

Third, you say "the application often uses just simple links and this can not easily be changed", with the implication that these links (resulting in simple GET requests) are state-changing. That's bad, both because it's a violation of the HTTP spec (see section 9 of https://www.ietf.org/rfc/rfc2616.txt), and because it violates the expectations of web client software (e.g. some browsers and other web clients pre-fetch links that a user can see, for reasons such as checking them for malware or making navigation to a pre-fetched link "instant"). If you really can't do without it, and can't attach JS to each link to send a client-controlled value, then SameSite is probably your only viable option for CSRF protection (and the approach you describe for users with a session makes sense), but that's a bad state to be in.

Fourth, SameSite isn't nearly as strong a protection as many people assume. Even leaving aside browsers that don't implement it (RIP IE, but some people still use it or ancient Android versions or so on), the scope of a "site" is far broader than the browser's concept of an Origin (as seen in features like same-origin policy, cross-origin resource sharing, and the Origin header). Origin requires a strict match on protocol, domain, and port. Site ignores protocol and port, and handles domain in an odd way: the "site" for a domain is determined by the first element of the domain that has a suffix on the Public Suffix List. In practice, this means that all subdomains of your site are going to be considered the same site for purposes of the SameSite flag (unless your site is itself a public suffix, as e.g. github.io is), and if an attacker can carry out a subdomain takeover attack (or get the victim to access your site over a different port or protocol), the attacker can set cookies and possibly read existing ones.

Some final notes:

  • There's no need to go out of your way to do constant-time comparisons of HMACs; the computation of an HMAC (or any other cryptographically secure hash) makes iterative timing attacks impossible (the attacker can't vary the input in such a way that there's a single controllable change in the output.
  • For comparing a random value in an anti-CSRF cookie against the same value in server-side state, though, you should use a constant-time check. Alternatively, hash/HMAC the value from the cookie (and store the hash/HMAC in the server-side state) to get the built-in protection against timing attacks and also provide a trivial bit of additional protection in the event of somebody getting read access on your DB.
  • There exists a header called Sec-Fetch-Site, which you can use to tell whether a request was made same-origin, cross-origin but same-site, cross-site, or by the user directly selecting a URL (by typing, opening a bookmark, etc. as opposed to navigating from another page by clicking a link, submitting a form, being navigated by script, etc.). Its even less supported than SameSite (Safari doesn't send it, and on browsers that do you need a more recent version), and opening a link from another app (e.g. a chat or email client) looks like the user entering the URL rather than like the user navigating from somewhere (which wouldn't matter, security-wise, if your GETs were safe like they're supposed to be), but for defense-in-depth it might help some?
15
  • the stateless situation is given in some places where people can add items to a list and for the login. The login for is in the header of each page and sometimes cached. Do I really not need some csrf protection for this? e.g. see stackoverflow.com/questions/6412813/… it sais otherwise
    – key_
    Commented Jun 26, 2022 at 11:24
  • I'm unclear what distinction you're drawing between login being cached vs. creating a session, and it's probably dangerous (caching + authentication is in general a risky combo). As for login CSRF, it's fairly rare for that to be a problem, but on some sites (specifically, ones where a user might not know what info to expect to find, or where the site doesn't make it clear when your session changes) it matters. In those cases, though, the standard solution is to create a session - just, an anonymous one - as alluded to in the paragraph starting "Second".
    – CBHacking
    Commented Jun 27, 2022 at 0:39
  • 1
    @Tanckom An HMAC - like the output of any secure hash function - can't be iteratively brute-forced. If you find an input that takes a little longer to reject (meaning the HMAC is a partial match), that's completely useless information because any change you make to any single bit anywhere in the input will completely change the HMAC. Even if by completely absurd luck you managed to get all but the last byte of the HMAC to collide, you'd still have to brute-force the entire HMAC to get every single byte to match. Thus, timing attacks just aren't viable; there's no way to detect progress.
    – CBHacking
    Commented Apr 15, 2023 at 19:11
  • 1
    Timing attacks are a generic class, but in the context of string/buffer comparison, you're basically right. However, in this case the attacker can't control a, b, etc.! The value that the attacker inputs is being hashed (HMACed, but that's just a keyed hash) before comparison, so the attacker can't choose, or even control, what value is being compared. Imagine the process of trying. 1 in 256 inputs will hash such that the first byte matches, great. But you can't iterate on that, locking the matched byte in place and trying the next one, you have to brute force the whole thing at once.
    – CBHacking
    Commented Apr 18, 2023 at 4:52
  • 1
    If the data to be MACed is static, and the [H]MAC is attacker-provided, then a timing attack is possible (unless the owner re-hashes both the computed and provided MACs before comparison, or uses a constant-time comparison). In the case where the data to be MACed is attacker-provided and the MAC is static, timing-aided brute-force is impossible. In cases where both are provided by the attacker - which is true for JWTs and similar; I may have been a little hasty to dismiss this - the attacker could hold the data constant and vary only the MAC, trying to iteratively brute-force it.
    – CBHacking
    Commented Apr 22, 2023 at 2:25
0

You can not stick only with samesite, it has its own limitations and overall we can not rely on this cookie flag for complete mitigation for CSRF Vulnerability. You have to use anti-CSRF Tokens.

You can create anti-CSRF tokens and places in these areas -

  • Customer Request header - such as - X-CSRF-Token (Best). But Not in cookie header
  • POST request parameters.
  • GET parameters

PLEASE NOTE: Validation on server-side for the token is a MUST

1
  • thank, I will do so, but token in GET parameter is not ok because it could be exposed during screenshare and within log files, so I will stick to POST and somehow think about to change all the links into form and rewite existing scripts to aet the header.
    – key_
    Commented Jun 29, 2022 at 13:58

You must log in to answer this question.

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