We ran into an issue with the django.middleware.csrf.CsrfViewMiddleware handling of secure posts. We'd like to be able to display a form on an insecure page, but have the form post occur over an SSL protected connection. This is currently impossible with the CSRF middleware enabled due to strict referer checking for secure posts. I filed ticket #13849[0] about this, but have not yet provided a patch as I'd rather wait to hear what you all have to say.
Our current solution (to avoid patching our Django) is to subclass the middleware, override the process_view method, and temporarily replace "http://" with "https://" in the referer in request.META if it's a secure post [1]. This allows the hostname check to proceed, but not deny the post merely because it came from an insecure page, and not force us to remove CSRF protection from all such views.
This solution, while it's working for now, is obviously not optimal. We'd like for Django to possibly provide an option (setting) to allow for the less strict check, or back off on checking for secure posts coming from secure referers at all. The check for same domain is good, but the check that the referer specifically start with "https://" seems excessive. The comments in the code above said check[2] suggests that the original developer thought it may be too strict as well.
If it's decided that this is worth addressing and a preferred solution is determined, I'd be more than happy to provide a patch. I just didn't want to go to the trouble of updating the middleware, writing tests, creating a new setting, and writing docs for that setting, before I heard from you guys whether this was even a good idea.
On Tue, Jun 29, 2010 at 16:54, Paul McLanahan <pmclana...@gmail.com> wrote: > We'd like to be able to display a form on an insecure page, but have > the form post occur over an SSL protected connection.
Thanks for the reply. I realize that this attack is an outside possibility. Were I running Facebook I would be concerned. If this is a larger threat than it seems then this should definitely stay in Django to discourage people like us. However, poisoning an arp cache to insert an attacker machine as a proxy which can then rewrite "https" to "http" in our page code isn't trivial, and if someone is doing it, I'd imagine they've got a larger and more lucrative target in mind. That said, we'll definitely keep it in mind and look at securing the pages containing the forms as well. For now though, I think the option of submitting a form over SSL from a non-SSL page would be nice to have.
On Tue, 2010-06-29 at 13:10 -0400, Paul McLanahan wrote: > On Tue, Jun 29, 2010 at 11:40 AM, TiNo <tin...@gmail.com> wrote: > > This is supposedly potentially insecure, see: > > http://samsclass.info/defcon.html (bottom of the page) > > or http://vimeo.com/7618090 from 2:45' > > Just my 2 cents, > > Tino
> Thanks for the reply. I realize that this attack is an outside > possibility. Were I running Facebook I would be concerned. If this is > a larger threat than it seems then this should definitely stay in > Django to discourage people like us. However, poisoning an arp cache > to insert an attacker machine as a proxy which can then rewrite > "https" to "http" in our page code isn't trivial, and if someone is > doing it, I'd imagine they've got a larger and more lucrative target > in mind. That said, we'll definitely keep it in mind and look at > securing the pages containing the forms as well. For now though, I > think the option of submitting a form over SSL from a non-SSL page > would be nice to have.
I wrote the original code and the comment about whether the checking was too strict or not, because, without thinking through all the implications, I wanted to err on the side of caution, and now you are forcing the issue - that's good!
In the context of the CSRF code, the whole point of the additional strict Referer checking for HTTPS is to defeat a man-in-the-middle attack:
- user browses to http://example.com/ - a MITM modifies the page that is returned, so that is has a POST form which targets https://example.com/detonate-bomb/ . The MITM has to include a CSRF token, but that's not a problem because he can invent one and send a CSRF cookie to match. - the POST form is submitted by javascript from the user's browser and so includes the CSRF cookie, a matching CSRF token and the user's session cookie, and so will be accepted.
Thus the CSRF token method fails if you have a MITM. We mitigate this by adding the strict Referer checking, but it only works if it treats an HTTP Referer as completely untrusted, just like an external site, which it is in this context. In the context of https://example.com/ , http://example.com/ is a completely unknown quantity, because with the SSL threat model we are allowing for the possibility of MITM. So if we loosen this check, we destroy the whole reason for it being there, and open up a big security hole.
In addition, having looked at the links that Tino sent, I now think there is a very big advantage to not allowing HTTP -> HTTPS POST requests by default, even ignoring the CSRF issue.
BTW, if you are using Django's sessions over HTTPS, then you need to have SESSION_COOKIE_SECURE = True, otherwise you are wide open to an HTTP-snooper hijacking HTTPS sessions. However, with SESSION_COOKIE_SECURE = True, sessions will not work over HTTP. This means you need to serve your whole site over HTTPS. I only just realised this for one of my own sites. We probably need to document that more clearly, but I'm not sure how.
Regards,
Luke
-- "The only skills I have the patience to learn are those that have no real application in life." (Calvin and Hobbes)
On Wed, Jun 30, 2010 at 7:45 AM, Luke Plant <L.Plant...@cantab.net> wrote: > In addition, having looked at the links that Tino sent, I now think > there is a very big advantage to not allowing HTTP -> HTTPS POST > requests by default, even ignoring the CSRF issue.
I agree that not allowing them by default is good, but I think that having the option to allow them is also good. I like the default behavior as it is, as it's inline with our "secure by default" philosophy. But in our situation, I'm just not as concerned with this because our only other alternative is to turn off CSRF checking for these views, which I think is an even worse idea.
I will say that I'm not sure our situation even benefits from CSRF protection. We're doing this on a login page (so they shouldn't have a session at all yet), and on other pages on which we ask users to submit their password (password change, username change, etc.). The later all result in confirmation emails, which should prevent fraud. We're still hesitant to remove CSRF protection from these views though just in case we've done something else that may open a hole that the CSRF mechanism will close. Like you said, we're trying to "err on the side of caution."
> BTW, if you are using Django's sessions over HTTPS, then you need to > have SESSION_COOKIE_SECURE = True, otherwise you are wide open to an > HTTP-snooper hijacking HTTPS sessions. However, with > SESSION_COOKIE_SECURE = True, sessions will not work over HTTP. This > means you need to serve your whole site over HTTPS. I only just > realised this for one of my own sites. We probably need to document > that more clearly, but I'm not sure how.
I can appreciate this as well, but we definitely don't need for the whole site to be secure. No personal or sensitive data is transmitted beyond the user's email address and password. So we'd really like to only secure the transmission of that info. If an attacker were to hijack one of our user's sessions, secure or otherwise, they'd just be able to use our site w/o paying for a few hours. If they tried to change the username or password, a confirmation email system would prevent the change. As with most web security techniques, we're just trying to be as secure as we can be without being overly inconvenient to our users, while not being low-hanging fruit for would-be attackers.
If you're all vehemently opposed to allowing the possibility of using the CSRF middleware and submitting over SSL from a non-SSL page, then we'll be fine. I just still think that this is a valid thing for a web app to want to do, and thus would be a good option for a user of the CSRF system to have.
On Wed, 2010-06-30 at 11:08 -0400, Paul McLanahan wrote: > I agree that not allowing them by default is good, but I think that > having the option to allow them is also good. I like the default > behavior as it is, as it's inline with our "secure by default" > philosophy. But in our situation, I'm just not as concerned with this > because our only other alternative is to turn off CSRF checking for > these views, which I think is an even worse idea.
> I will say that I'm not sure our situation even benefits from CSRF > protection. We're doing this on a login page (so they shouldn't have a > session at all yet), and on other pages on which we ask users to > submit their password (password change, username change, etc.). The > later all result in confirmation emails, which should prevent fraud. > We're still hesitant to remove CSRF protection from these views though > just in case we've done something else that may open a hole that the > CSRF mechanism will close. Like you said, we're trying to "err on the > side of caution."
With Django's sessions and login method, you *do* have a session on the login page itself i.e. before the actual login step. So you need to worry about session fixation etc. Even if not, and you are using your own login method, there is still "login CSRF" to worry about [1].
Without the check for a HTTPS referer, you are wide open to a MITM attacker doing CSRF on your HTTPS connections. The check isn't "erring on the side of caution" — that was what I *previously* thought it was, having not thought through the possible attacks, but in fact it is *absolutely essential*. Without it, you should consider your site as having the same level of protection as an HTTP site.
Yes, there are still some benefits to HTTPS (passwords not sent in plaintext), but you certainly don't have the kind of protection that would be expected in HTTPS, and in the general case you might find that all benefits are destroyed by what a successful attacker might be able to achieve (setting passwords etc.) unless you are extremely careful and have closed every possible loophole. For these reasons, changing this behaviour — or even allowing it as an option — would be craziness for a general purpose framework. An option is a bad idea in general, because it is global — and we certainly do *not* want this hole punched in the admin site.
If you can really live with the security holes you want to make, that is absolutely up to you. You can write your own middleware, and ensure its suitability for your needs. But I cannot see an argument for supporting this as an option out of the box.
On Wed, Jun 30, 2010 at 12:16 PM, Luke Plant <L.Plant...@cantab.net> wrote: > With Django's sessions and login method, you *do* have a session on the > login page itself i.e. before the actual login step. So you need to > worry about session fixation etc. Even if not, and you are using your > own login method, there is still "login CSRF" to worry about [1].
> Without the check for a HTTPS referer, you are wide open to a MITM > attacker doing CSRF on your HTTPS connections. The check isn't "erring > on the side of caution" — that was what I *previously* thought it was, > having not thought through the possible attacks, but in fact it is > *absolutely essential*. Without it, you should consider your site as > having the same level of protection as an HTTP site.
> Yes, there are still some benefits to HTTPS (passwords not sent in > plaintext), but you certainly don't have the kind of protection that > would be expected in HTTPS, and in the general case you might find that > all benefits are destroyed by what a successful attacker might be able > to achieve (setting passwords etc.) unless you are extremely careful and > have closed every possible loophole. For these reasons, changing this > behaviour — or even allowing it as an option — would be craziness for a > general purpose framework. An option is a bad idea in general, because > it is global — and we certainly do *not* want this hole punched in the > admin site.
> If you can really live with the security holes you want to make, that is > absolutely up to you. You can write your own middleware, and ensure its > suitability for your needs. But I cannot see an argument for supporting > this as an option out of the box.
Fair enough. If the situation is potentially really this dire, as you've convinced me that it is, then leaving it alone is the way to go.