Hey Shai - I have no objections to this change. I think it's got a slight whiff of security theatre, in that it *looks* like it adds more protection than it *actually* does. However, I, too, have spent a ton of time talking auditors down from "OMG Django is vulnerable to CSRF!" and I'd like to do less of that. I like that rotating SECRET_KEY invalidates CSRF tokens.
Time-limiting is a nice feature too, actually. Again the perceived security is higher than the actual added security, but the general principle of giving people more control is a good one. I'm sure there's some audit checklist out there that has "CSRF tokens must not be valid for longer than X hours" or something on it, and helping our users tick those boxes isn't such a bad thing. So yeah, lukewarm praise from me at best, but since there's at least a bit of real improvement here I see no reason this shouldn't go in. +1 from me. Jacob On Sat, Jul 27, 2013 at 6:12 PM, Shai Berger <[email protected]> wrote: > Hi everybody, > > TL;DR: A simple change can make Django's CSRF protection a little better; > an > additional, slightly less simple one, can also make it look better. > > Django's CSRF protection scheme is a bit unusual; unlike most such > schemes, it > does not rely on a value stored in the server that needs to be matched by a > submitted token and is replaced with every submission, but rather on a > constant value stored in a cookie. This generally works (for details of how > and under what conditions exactly, see [1]), but has two minor problems: > > 1) It is unusual, and in particular diverges from what OWASP[2] > recommends[3]; > as a result, security analysts often think it is not secure. They have been > proven wrong in all cases members of core are aware of, but proving it > again > and again is a nuisance, and there may be bad PR related to this. > > 2) It carries a "second-order" vulnerability: If your site has been > compromised (XSS, Man-in-the-middle, or server compromise) then you become > persistently vulnerable to CSRF. All of these vulnerabilities are way worse > than CSRF and render all CSRF protection schemes worthless while they last; > the point is *not* that they allow CSRF, but rather that they allow CSRF > to be > performed after the main hole has been plugged. This is because the > attacker > can use the main vulnerability to "steal", or even set, csrftoken cookie > values, which they can then use later. After a successful attack of this > magnitude, you need to reset the csrftoken cookies of all users, and this > is > neither obvious nor straightforward to do. > > Django's unique scheme does have two advantages over the more common > solutions, which we would like to keep: > > 1) It is not tied to sessions, users, or site-stored per-user data, > allowing > CSRF protection to a wider range of users > > 2) It avoids the problem of having only one "current" token, which causes > the > submission of one form to invalidate forms open in other browser tabs. > > To improve on both problem issues, while keeping the advantages, I suggest > the > following modifications: > > a) Use a signed cookie for csrftoken -- using Django's existing signing > facility[4], this means signing the cookie with the SECRET_KEY from the > settings; so that an attacker cannot set arbitrary cookies, and changing > the > SECRET_KEY after a compromise immeiately invalidates csrftoken cookies. > > b) Optionally allowing time-limited CSRF tokens. Such tokens will be > generated > by adding a parameter of maximum age to the csrftoken tag, and by marking > view > methods (specifically with a decorator, or globally with a setting) as > requiring timed tokens. When this is used, the posted token value will > need to > be different from the cookie value -- to keep advantage 2, the cookie will > still be constant, and expiry time will only be present in the submitted > token[5]. This method breaks the current way we do CSRF-protected AJAX, so > it > will likely stay optional (and opt-in). > > As you may guess, signing the cookie adds an actual iota of security. > Adding > expiry adds very little -- if an attacker has access to the cookie, they > can > usually just ask the site to generate valid tokens for them, so getting any > real protection will require annoyingly short expiry times. But the fact > that > an attacker needs this extra step makes it a tiny bit harder for them and > makes their actions a tiny bit more detectable; and having a constantly- > changing CSRF token may make the whole thing look a little better to naive > analysts. > > I had some help and guidance in drafting this proposal -- you can credit > Donald Stufft, mostly, for any egregious blunder I didn't make. I am still > responsible for the ones I did make. > > Your comments are welcome, > > Shai. > > > [1] https://docs.djangoproject.com/en/dev/ref/contrib/csrf/ -- in > particular, > "how it works" and "limitations" > > [2] https://www.owasp.org > > [3] > https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet > > [4] https://docs.djangoproject.com/en/dev/topics/signing/ > > [5] django.core.signing.TimestampSigner signs content with the time of the > signature, and then takes a max_age in its unsign() method; the suggested > method would go the other way around, timestamping the token with the time > of > expiry, to allow checking without using data stored on the server (and to > allow different forms to use different max-age values). > > -- > You received this message because you are subscribed to the Google Groups > "Django developers" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at http://groups.google.com/group/django-developers. > For more options, visit https://groups.google.com/groups/opt_out. > > > -- You received this message because you are subscribed to the Google Groups "Django developers" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at http://groups.google.com/group/django-developers. For more options, visit https://groups.google.com/groups/opt_out.
