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?

key_
  • 1
  • 4
  • welcome - please clarify whether `R` contains any timestamp data, and if not, whether it could be modified? – brynk Jun 22 '22 at 17:01
  • it does not, should I add a timestamp? – key_ Jun 22 '22 at 18:13
  • i asked because this (or tieing to the session) would likely contribute to a compromise/ partial solution (if one is possible!? https://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. https://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 Jun 22 '22 at 18:59
  • 1
    and more: https://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 Jun 22 '22 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) – pcalkins Jun 22 '22 at 19:21
  • @pcalkins there is no form, in some cases there are simple links. The session cookie is set to lax (for normal users) because they do have more comfort when entering the page from 3rd party websites e.g. from google search result, so they still logged in. I think I do not get the last point. – key_ Jun 22 '22 at 21:03
  • I don't think you should ever be changing any state using GET requests. That sounds like trouble. What I mean by static is an "app" or "site" that impersonates yours can't be fixed. It would need to insert the token into it's DOM in order to send the request on behalf of the user (the cookie would be sent automatically since it's sent to the same domain, but not the hidden field) Though an iframe loaded with the original site might work? – pcalkins Jun 22 '22 at 22:06
  • @pcalkins Iframe is forced to samesite by security headers. Anyway as far I know the browser would not automatically send the samesite=strict cookie within a iframe that is hosted on another domain. – key_ Jun 25 '22 at 16:42
  • I found a similar question here https://security.stackexchange.com/questions/201396/samesite-cookie-attribute-and-synchronizer-token-pattern?rq=1 – key_ Jun 26 '22 at 07:19

2 Answers2

1

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?
CBHacking
  • 42,359
  • 3
  • 76
  • 107
  • 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 https://stackoverflow.com/questions/6412813/do-login-forms-need-tokens-against-csrf-attacks it sais otherwise – key_ Jun 26 '22 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 Jun 27 '22 at 00:39
  • Also, login absolutely needs to be POST - or at least not GET - and you can presumably put traditional anti-CSRF protections in place there. Of course, they still need a session to anchor on, so you will still need some sort of anonymous session. – CBHacking Jun 27 '22 at 00:41
  • If you use an anonymous session, make sure to start a new session once authenticated. (session-fixation attack: https://owasp.org/www-community/attacks/Session_fixation ) – pcalkins Jun 27 '22 at 21:49
  • @CBHacking thank you, please understand I am limited in what am allowed to changed in that application because I have to fix things using plugins. The login form can use POST and csrf token but on the initial request I am not able to create a pre-session because login form is included on pages that are cached in proxies and login form in on every page as part of the header. – key_ Jun 28 '22 at 03:56
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

Sir Muffington
  • 1,536
  • 2
  • 11
  • 23
  • 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_ Jun 29 '22 at 13:58