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 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 . 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 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.
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.
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
- 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.
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.
"The only skills I have the patience to learn are those that have
no real application in life." (Calvin and Hobbes)
Luke Plant || http://lukeplant.me.uk/
Thanks for the reply. See my comments below.
On Wed, Jun 30, 2010 at 7:45 AM, Luke Plant <L.Pla...@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
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.
> 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 .
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
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
Thanks again for your time.