Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discuss and document what to do about CSRF #162

Closed
ustun opened this issue Nov 2, 2016 · 6 comments
Closed

Discuss and document what to do about CSRF #162

ustun opened this issue Nov 2, 2016 · 6 comments

Comments

@ustun
Copy link

ustun commented Nov 2, 2016

This is a continuation of the discussion at #57

Consider a modern stack where there is a frontend application that is completely decoupled from the API server.

The cookies are still saved in the browser on the API server, and they are sent with the request if "withCredentials" flag are set. So far, so good. (Maybe we should add some documentation regarding that too, setting ALLOW_CREDENTIALS server side should be coupled with setting withCredentials flag in the JS requests.)

CSRF and CORS are at somewhat odds though, since one is about preventing cross site requests that come from unauthorized sources, whereas the other is a mechanism for accepting requests coming from authorized sources.

When CORS is enabled via this library, there are currently no recommendations on how to deal with this however. By default, even if a cross site is added to the whitelist, and potentially unsafe methods like POST, PUT are allowed (already enabled by default), the API server will reject incoming POST requests due to missing CSRF token data.

There are a few different approaches that could be suggested:

1- Since we are explicitly allowing a cross-site request, we can suggest the user to disable CSRF on those endpoints. However, CSRF protection should only be disabled if the request is coming from a whitelisted origin. Can we just check the Origin header there? AFAIK Origin header cannot be faked in a browser setting.

2- We could also suggest that the user disabled CSRF protection when the content type is JSON. This has a caveat mentioned here:

Just an FYI, Flash allows setting the Content-Type for cross-origin requests as shown here: http://saynotolinux.com/tests/flash-contenttype.html . You still need a CSRF token or custom header even if your endpoint requires a Content-Type of 'application/json'.

http://homakov.blogspot.com.tr/2012/06/x-www-form-urlencoded-vs-json-pros-and.html

There the OP originally suggested we can leave off CSRF token when we do this, but says he changed his mind afterwards. Not sure what his current stance is. /cc @homakov Could you give a recommendation here?

3- We could suggest the user to expose a /csrf endpoint. This endpoint will be hit by the cross-site before any POST request (once when the site is loaded, or everytime before a POST request). Then, the subsequent POST request should send this csrf token via a csrf header (X-CSRF). This /csrf endpoint should be protected against cross origin requests from other sites, otherwise a malicious site can similarly retrieve the token via AJAX, and send that with the POST data.

4- There might be some discussion of old vs modern browsers regarding CSRF and CORS.

@homakov
Copy link

homakov commented Nov 4, 2016

The current recommendation is http://sakurity.com/blog/2015/03/04/hybrid_api_auth.html

If valid CSRF token was provided, allow reading from cookies. If not, drop cookies (treat as non browser/API request). No errors, no hassle.

@ustun
Copy link
Author

ustun commented Nov 4, 2016

Thank you for your input @homakov

@ustun
Copy link
Author

ustun commented Nov 4, 2016

@homakov Could you elaborate on the last section in your article?

When you say "drop CORS", do you mean do not support CORS at all? Or do you mean CORS requests should be done with "withCredentials: true" and not via other mechanism like JWT?

Also, what if we don't support JSONP at all, and allow CORS for only a known server managed by us?

Also, you say "If valid CSRF token was provided, allow reading from cookies", but how is the consumer site supposed to known the CSRF token without hitting /csrf? It can't read the cookies of api.example.com (actually, I guess it might if they are on the same domain, but on different subdomains if the cookies are configured correctly, i.e. the cookie is on the bare domain, not on the subdomain.)

@adamchainz
Copy link
Owner

If not, drop cookies (treat as non browser/API request)

This doesn't seem to have anything to do with CORS which prevents requests on the browser side

@jonathan-golorry
Copy link

jonathan-golorry commented Oct 13, 2018

I have my React SPA and Django Rest API on two different domains. I'd like to use session auth on my API instead of linking a session on the SPA to a token on the API. I think I have CORS correctly configured. I get the right preflights and am sending the sessionid cookie from the SPA to the API.

The issue is that my SPA can't read the csrftoken cookie in order to set the X-CSRFToken header, so I'm getting {"detail":"CSRF Failed: CSRF token missing or incorrect."}.

I think the hybrid approach @homakov is suggesting is that we prioritise token authentication, falling back to session auth (cookies in general) as long as there is a valid CSRF token. The question is, how do we access that CSRF token? My thoughts on the list from @ustun

  1. This relies on browsers being secure when it comes to origin/referer. I know edge fails to set origin for a cross-domain POST form. I'd suggest falling back to referer, but I've also heard Edge allows a spoofed referer on GET requests. https://security.stackexchange.com/questions/191301/origin-header-vs-token-based-mitigation

  2. I don't see any advantages of this over option 1 and it's probably less secure.

  3. Essentially the same as 1. Any domain that has the right origin can pass CORS and will be able to get a token. Managing/saving/refreshing the token would be a pain, but at least it would mitigate the problems with Edge.

I've also heard of someone including the CSRF token in a custom response header, so the SPA has access to it. I think that's just to avoid having to disable CSRF validation, since it would mean any domain that passes CORS checking can get the token.

I think option 1 is the best. That seems to be the vision on the Mozilla forums https://bugzilla.mozilla.org/show_bug.cgi?id=446344

If you really need cross-domain forms, option 3 might be good. Django's CSRF_USE_SESSIONS might be a better solution to that, though.

Actually patching the CSRF checking is a bit of a pain. It would be nice if there was an option to skip token checks when the origin is in CSRF_TRUSTED_ORIGINS.

@udemezue01

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants