These guys are not the only ones to make this mistake. Check the first line of Tornado's XSRF check:
def check_xsrf_cookie(self):
"""Verifies that the '_xsrf' cookie matches the '_xsrf' argument.
To prevent cross-site request forgery, we set an '_xsrf' cookie
and include the same '_xsrf' value as an argument with all POST
requests. If the two do not match, we reject the form submission
as a potential forgery.
See http://en.wikipedia.org/wiki/Cross-site_request_forgery
"""
if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
return
token = self.get_argument("_xsrf", None)
if not token:
raise HTTPError(403, "'_xsrf' argument missing from POST")
if self.xsrf_token != token:
raise HTTPError(403, "XSRF cookie does not match POST argument")
I wouldn't call it a mistake. If you had asked me before this afternoon whether trusting X-Requested-With would protect against CSRF, I would have said yes. I still have no idea how you can send arbitrary cross-domain requests in Java and Flash: the fact that you can do so is a security vulnerability in and of itself.
That being said, I'm going to let them know to fix that code. ;)
Since this also affected Rails, a minor clarification:
We spoke with Ben Bangert of Pylons/Pyramid, and did some checking of source code there and in other projects, and as far as we knew last week, Django was the only Python framework affected by the CSRF issue. If you find another project which is affected, please notify them ASAP.
This is correct. So long as the plugins themselves are using jQuery's own $.ajax() method (or one of its derivatives that in turn call it), then anything in ajaxSetup will be reflected in those requests.
As discussed on another thread, shipping bcrypt by default isn't technically feasible yet due to the Python binding for it failing to compile cleanly on Windows: http://news.ycombinator.com/item?id=2177989
I've been looking at this recently. I'm still not sure about using bcrypt (SHA512 for example is part of the python standard library), but we should at least be using SHA2 instead of SHA1. One possibility is to use google's KeyGen to encrypt the password, but bottom line, I think that the password hashing should be the responsibility of the backend (with the default doing the "right" thing).
I've also been looking at trying to make an username===email backend for django (instead of a user generated--part of the battle of avoiding too many user names that people just forget) and it's hard because a lot of things in the auth module are fairly fixed--regardless of the backend that you choose (for example, the regex on the username, or the length of fields). There have been some other attempts (like emailauth) to address this, but it's actually a large undertaking if you want the same functionality as the auth module.
How responsive are the django developers? They seemed fairly certain that they weren't going to change the defaults for the username/email address to preserve backwards compatibility. Is it better to make a whole new authorization module (complete with middleware) or to patch theirs?
Why couldn't they use SHA2 and make it go around X times? The bcrypt plugin [1] maintains backwards compatibility with old passwords just fine.
Also, if you are cool with rolling your own registration forms/etc. you can easily just set the email as the username and email. You lose the more obscure but technically valid characters for email (a-z A-Z 0-9 @.+-_ are all fine), but 99% of emails work fine in the Django username field. Or maybe I just haven't hit some obvious problem with that implementation yet...
A few reasons:
1) I work at an international user facility for my day job and I really only want to do this once ;> With IDNs, I'm worried about dealing with Unicode IDNs. The email regex has been updated in recent versions of django to support this.
2) The length--technically, email address can be 256 characters long (http://tools.ietf.org/html/rfc5321#section-4.5.3). The default length of email addresses in django is 75 characters and usernames are 30 characters. This is something that I want to specify (I don't need to be backwards compatible with any existing django databases).
3)
As for using SHA2 and stretching, or bcrypt, I think that they should be choices and that the auth package should simply ask the backend what to do. It's not just limited to the user's choice of password, but also to sending out tokens that are only using SHA1. If we want to use say bcrypt for those, then an authentication backend should be required to have a token generator that can be called. The default backend should simply do what the django development team thinks is best. Forms should also be the responsibility of the backend....
BUT
The problem with forking contrib.auth to make say for example "gen_auth" is that the module is pretty good (so it's a lot of effort to maintain a fork and to stay current with the django trunk) and so far I've found that the following modules rely on it:
comments
sites
sitemaps (perhaps)
messages (ok, deprecated by 1.4, so I'll ignore it)
flatpages
(and I don't know how many other 3rd party packages).
It's not ideal, but the more I look at it, the more I'm leaning towards monkey_patching.
Ah, thanks for the lengthy description. Your reminder that usernames can only be 30 chars led me to a fix [1]. It's definitely monkey patching, but it works fairly well.