@tom and @adamchainz, thank you both for your thoughts, the quotes, and the links, and sorry for the novel I’m throwing at you now.
I’ll try to address some comments in ways that I hope are helpful to the discussion, before I launch into a pretty lengthy analysis of this OWASP recommendation and the references they used to support it.
Personally I don’t believe the tokens add very much extra friction at all, relative to their benefit.
I think the tokens add a little friction.
I agree with both of you. The tokens add relatively little friction, and historically that small amount of fiction has been well worth the benefit they provide. However, if the benefit is reduced, it is warranted to reconsider whether the benefit continues to outweigh the cost of the friction.
Even small amounts of unnecessary friction can hinder those we most want to join our community. We value diversity, and removing the friction to begin and grow is critical to enabling diversity to join our community. This is only one small papercut, but if we remove these small papercuts one at a time we end up with a framework that has spent a lot of time caring about the people who may otherwise be left out or frustrated if we did not.
So the new cookie specification continues to recommend tokens, and I think we should follow that, along with OWASP’s recommendation.
I believe this is a misread of this section, and one that I initially made too as I was digging in. I go into greater detail below, but succinctly, this section isn’t talking about CSRF tokens, it’s talking about Strict vs Lax enforcement and an interesting “two-cookie” mechanism for separating read authorization and write authorization.
One other thing I would add here. We’re a large framework. We have hundreds of millions, if not billions of end users around the world. 3% of this is still a very large number of people. It’s true we only support modern browsers, but this shouldn’t go so far that we needlessly jeopardise the security of tens of millions (or more) of people.
This is a very wise consideration, and is the crux of the judgement call we need to make here. We must determine if the benefit is worth the risk to those using our framework and the people who use Django-powered sites.
I did attempt to be modest in my proposal, though. I do not think that this approach is one we want to encourage everyone to make, especially not immediately. While I can imagine us encouraging it more as time progresses and SameSite cookies become ubiquitous, and I don’t want to pretend that will never happen, I really don’t know what kind of timeframe would be appropriate for that, if any, or how we should make that determination.
What I’m proposing is that I, and those that believe they are like me, have the ability to make this assessment without needing to rewrite parts of Django in order to get the other benefits that are included in our CSRF protection, namely the Origin and Referrer header checking.
Now, it could be that even this is unwise. That given this lever, too many people would be inclined to pull the lever even when it could cause them real problems. I’m not sure that’s the case, and if we are to ever get to relying on Lax enforcement we’ll have to do it eventually, but perhaps now isn’t the time to introduce the lever. It seems to me likely that we’d want to get there, if for no other reason than that if browser support were ubiquitous, as we expect it to be at some point in the future, we would likely not have compelling reason to include this feature in Django core.
If now isn’t the time because 3% is still too dangerous, I’d appreciate a better understanding of what would be an acceptable percentage of global users that are on browsers that don’t support SameSite=Lax to where it would be the right time to add the lever. I really don’t have a good sense of what Django should think about that question.
I wonder though, with the clearer target of removing the need to include the {% csrf_token %}
template tag, perhaps an alternative proposal could be more modest yet than my original:
- Add a new setting
CSRF_AUTO_CHECK_TOKEN
that defaults to True
- When
False
would not check the CSRF token for views not decorated explicitly with @csrf_protect
I’m not sure that would alleviate your concerns sufficiently, but it would mean that things that explicitly annotate CSRF protection at the view level, like the Django admin, would still check the CSRF token.
If you want we could invite other members of the security team into the thread.
I don’t feel terribly comfortable doing that myself, but I’m all for having more brains looking at this idea that has been taking (probably too much) space in my mind for a while now.
OK, now for the OWASP recommendation analysis.
The OWASP recommendation gives two reasons for its recommendation against relying on SameSite cookies in lieu of CSRF tokens.
The first is the prevalence of browser support (“protects the user through the browsers supporting it”). Depending on your reading of it, this can be referring to an illegitimate concern, or one that we can reasonably expect to be reduced over time. I think the latter is the correct reading, but for the sake of others who may read this later and learn a bit about CSRF from this discussion, I do want to address the former anyway.
One way to read this concern of browser support is that if the browser isn’t secure, then there’s nothing that the server can do to mitigate CSRF attacks. Put in this simplistic way, it’s important to recognize that this is exactly the same as things would be with server-side CSRF checking. Either way, we very much rely on the security afforded by browsers. If the browser itself were untrustworthy, there is no amount of server-side patching we could ever do to make it safe for an end-user to use. Such is the state of any site that allows connections from clients that are not verified, and I think we have good reasons that we generally do not require that type of strict verification of browsers.
But again, I do not think this is what they meant: they meant that users are still using browsers that do not implement SameSite cookies. By its nature, we can expect that over time this concern will increasingly grow less relevant. For some this transition of their user base will go quickly, but for others their user base may more commonly be on older browsers.
This is a substantive concern, and is where the legitimate concerns that I’m aware of are located.
The second reason that OWASP gives are the “2 ways to bypass it as mentioned in the following section”. After having read that section and related sections to do my best to make sure I understand it correctly, I think that these “ways to bypass it” do not provide justification for this stance from Django, and I’m not sure they provide additional justification for this stance at all.
Before I explain why, I’ll note that even if OWASP is wrong about the security implications, the fact that OWASP makes the recommendation is something that Django should take into consideration. It’s just that the specifics of the choices we’re looking at make these “ways to bypass it” not relevant to the actual implementation in Django.
The section pointed to in the OWASP article is entitled “‘Strict’ and ‘Lax’ enforcement”. As the title might suggest, the purpose of the section is to discuss the relative strengths and weaknesses of using each of these modes for CSRF protection. I think that understanding this section on its own terms is important to evaluating the concerns raised by the OWASP recommendation.
In discussing the challenges associated with “Strict” enforcement, the authors point out that “Lax enforcement provides a reasonable defense in depth against CSRF attacks that rely on unsafe HTTP methods (like POST)”. Like most security-related documents, they are actively avoiding making recommendations against dropping other types of security, but it’s important to note that they are not making any particular statement about traditional CSRF tokens, and especially not saying that traditional CSRF tokens solve problems that SameSite does not.
Then they point out two particular areas of potentially confusing CSRF concern that “Lax” brings up in comparison with “Strict” enforcement: top-level navigation and pre-rendering. In both cases, these concerns would, as far as I can tell, apply equally to Django’s existing CSRF implementation using a token. Only a CSRF implementation analogous to “Strict” enforcement would alleviate the concerns presented here, and Django has made the conscious choice to align our CSRF protection the same way that the “Lax” enforcement does: based on “safe” HTTP methods.
They do recommend using “a session management mechanism such as described in Section 8.8.2”. Because that sounds vaguely like a recommendation to use a CSRF session token, I want to follow that thread, and see what they’re suggesting. Turns out, it’s kind of interesting, but also pretty irrelevant to the question of whether SameSite=Lax has some drawback compared to a CSRF token.
It’s worth a read, but essentially it’s suggesting using a “Lax” session cookie for read-only access, and a separate “Strict” session cookie that grants write access. It’s most important to note that it is not making any suggestion that a session CSRF token is still necessary. We can also note that this pattern is unrelated to the choice of using a CSRF token or relying on SameSite cookie enforcement.