Psifi-Solutions / csrf-csrf

A utility package to help implement stateless CSRF protection using the Double Submit Cookie Pattern in express.
Other
123 stars 19 forks source link

Why is setting a CSRF cookie or header not recommended? #50

Closed chr15m closed 9 months ago

chr15m commented 10 months ago

Hi, I'm reviewing csurf replacements (for Sitefox) and I came across your libraries. Thank you very much for taking the time to build these and put them into public for general use. :pray:

In your documentation on both modules it says:

Do not transmit your CSRF token by cookies.

And later;

You should only transmit your token to the frontend as part of a response payload, do not include the token in response headers or in a cookie, and do not transmit the token hash by any other means.

I am curious why this is? I notice that the current version of Django still advocates this approach:

https://docs.djangoproject.com/en/4.2/howto/csrf/#using-csrf-protection-with-ajax

Are they not following security best practices? Thank you for your time!

psibean commented 10 months ago

Not sure what you mean, either the header or form body is the recommended approach to send the token from the frontend to the backend. But you still need a way to send the token to the frontend.

As for the Django implementation, you'll see it says this:

Acquiring the token if CSRF_USE_SESSIONS and CSRF_COOKIE_HTTPONLY are false The recommended source for the token is the csrftoken cookie, which will be set if you’ve enabled CSRF protection for your views as outlined above.

For some reason they're recommending you to use a cookie when these two things are false. It's strange, because it should mostly be recommended to do this in a case where CSRF_USE_SESSIONS is true and CSRF_COOKIE_HTTPONLY is false, not when both are false. It's a little negligible if you have secure flag set. The idea is the Synchronised Token Pattern stores a generated token in the backend against the users session, then the value sent from the frontend is compared to the value in the backend. As the value is stored in the backend, there's no way to fake a value, it has to match.

Then they say this:

Acquiring the token if CSRF_USE_SESSIONS or CSRF_COOKIE_HTTPONLY is True If you activate CSRF_USE_SESSIONS or CSRF_COOKIE_HTTPONLY, you must include the CSRF token in your HTML and read the token from the DOM with JavaScript:

In this case, the main cookies is http only, but you could still send a copy of it by cookie if you wanted to. The point of the Double Submit Pattern is that the frontend sends two values. It sends a cookie, which should be httponly, then it sends an additional value either in a header or form body, the backend then compares both of those values in some way. The additional value could also be sent to the frontend in a regular cookie in this case

In addition, their documentation for CSRF_USE_SESSIONS says this:

Storing the CSRF token in a cookie (Django’s default) is safe, but storing it in the session is common practice in other web frameworks and therefore sometimes demanded by security auditors.

Django allows you to use a different pattern based on the configuration, to me it does appear that they have this backwards, but Django also has many other layers of middleware and security that a typical Node backend isn't going to have (unless the person configures and sets them all up themselves), so it's a completely different context.

Generally speaking, it can be fine to send the token by cookie, however see the discussion here regarding one incident where a subdomain was used to target the main domain, and had the cookie been httpOnly and used differently, this attack would not have been successful.

Django is a well-established framework, and I've worked with it professionally too, I would need to look deeper into the exact implementation of their CSRF protection to comment better on it, but based on their documentation alone, it does look like you could technically configure it and use it in a way that isn't secure. I try to avoid that possibility with csrf-csrf and csrf-sync, hence these implementations are provided separately, rather than together where the one you use depends on config.

Edit: Based on the Django implementation here, they compare the token from the request cookie with the value delivered by the frontend (via body or header), and in this case, OWASP does recommend that this cookie is http only. So by default, this isn't best practice, but it's up to the dev to ensure the configuration is appropriate for their needs and use case. Refer to the _get_secret and _check_token functions in the linked Django code.

As of course, if you have an XSS vuln, CSRF protection won't help you. But in the case of the subdomain example on twitter, there can still be cases that slip through and can be mitigated by ensuring you have the best practice settings.

Not following best practices doesn't necessarily mean insecure, it just means there's things that you potentially could be doing, which may or may not make a difference for your use case. I would not recommend Django's default configuration for anything other than a development environment, I would highly recommend using session based CSRF and ensuring secure and http only flags are true.

Django gives you the power to configure a content security policy and appropriate CORS settings out of the box. With Node, having these additional securities is optional and their utilisation is entirely up to the dev.