Take the 2-minute tour ×
Information Security Stack Exchange is a question and answer site for Information security professionals. It's 100% free, no registration required.

The EFF recommends using HTTPS everywhere on your site, and I'm sure this site would agree. When I asked a question about using Django to implement HTTPS on my login page, that was certainly the response I got :)

So I'm trying to do just that. I have a Django/nginx setup that I'm trying to configure for HTTPS-only - it's sort of working, but there are problems. More importantly, I'm sure if it's really secure, despite seeing the https prefix.

I have configured nginx to redirect all http pages to https, and that part works. However... Say I have a page, https://mysite.com/search/, with a search form/button on it. I click the button, Django processes the form, and does a redirect to a results page, which is http://mysite.com/search/results?term="foo".

This URL gets sent to the browser, which sends it back to the nginx server, which does a permanent redirect to an https-prefixed version of the page. (At least I think that's what is happening - certainly IE warns me that I'm going to an insecure page, and then right back to a secure page :)

But is this really secure? Or, at least as much security as a standard HTTPS-only site would have? Is the fact that Django transmits a http-prefix URL, someone compromising security? Yes, as far as I can tell, only pages that have an https-prefix get replied to, but it just doesn't feel right :) Security is funky, as this site can attest to, and I'm worried there's something I'm missing.

share|improve this question
    
How do you have the form's action attribute setup? If you force it to the full https url does that fix it? –  mikeazo Nov 16 '11 at 21:03
    
@mikeazo, the HTML syntax I use is ` <form action="." method="post" id="search">{% csrf_token %} `. The actual redirection is done in the Python view code, and the URL is determined by Django. –  John C Nov 16 '11 at 21:26
3  
do not serve any page on HTTP except for a page which either redirects to your secure site or displays a message with link to your secure site. And Only HTTPS is not equal to a secure site, HTTPS only insures that your traffic is encrypted between your browser and server nothing more. So do not ignore other things and build security into your application –  Sachin Kumar Nov 17 '11 at 6:26
    
@JohnC fixed (njinx) typo for nginx. –  dr jimbob Nov 17 '11 at 14:55

6 Answers 6

up vote 31 down vote accepted

Secure your cookies

In settings.py put the lines

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

and cookies will only be sent via HTTPS connections. Additionally, you probably also want SESSION_EXPIRE_AT_BROWSER_CLOSE=True. Note if you are using older versions of django (less than 1.4), there isn't a setting for secure CSRF cookies. As a quick fix, you can just have CSRF cookie be secure when the session cookie is secure (SESSION_COOKIE_SECURE=True), by editing django/middleware/csrf.py:

class CsrfViewMiddleware(object):
   ...
   def process_response(self, request, response):
       ...
       response.set_cookie(settings.CSRF_COOKIE_NAME,
            request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
            domain=settings.CSRF_COOKIE_DOMAIN,
            secure=settings.SESSION_COOKIE_SECURE or None)

Direct HTTP requests to HTTPS in the webserver

Next you want a rewrite rule that redirects http requests to https, e.g., in nginx

server {
   listen 80;
   rewrite ^(.*) https://$host$1 permanent;
}

Django's reverse function and url template tags only return relative links; so if you are on an https page your links will keep you on the https site.

Set OS environmental variable HTTPS to on

Finally, (and my original response excluded this), you need to enable the OS environmental variable HTTPS to 'on' so django will prepend https to fully generated links (e.g., like with HttpRedirectRequests). If you are using mod_wsgi, you can add the line:

os.environ['HTTPS'] = "on"

to your wsgi script. If you are using uwsgi, you can add an environmental variable by the command line switch --env HTTPS=on or by adding the line env = HTTPS=on to your uwsgi .ini file. As a last resort if nothing else works, you could edit your settings file to have the lines import os and os.environ['HTTPS'] = "on", which also should work.

If you are using wsgi, you may want to additionally set the environmental variable wsgi.url_scheme to 'https' by adding this to your settings.py :

os.environ['wsgi.url_scheme'] = 'https'

The wsgi advice courtesy of Vijayendra Bapte's comment.

You can see the need for this environmental variable by reading django/http/__init__.py:

def build_absolute_uri(self, location=None):
    """
    Builds an absolute URI from the location and the variables available in
    this request. If no location is specified, the absolute URI is built on
    ``request.get_full_path()``.
    """
    if not location:
        location = self.get_full_path()
    if not absolute_http_url_re.match(location):
        current_uri = '%s://%s%s' % (self.is_secure() and 'https' or 'http',
                                     self.get_host(), self.path)
        location = urljoin(current_uri, location)
    return iri_to_uri(location)

def is_secure(self):
    return os.environ.get("HTTPS") == "on"

Additional Web Server Things:

Take that guy's advice and turn on HSTS headers in your web server by adding a line to nginx:

add_header Strict-Transport-Security max-age=31536000;

This tells your web browser that your website for the next 10 years will be using HTTPS only. If there's any Man-in-the-middle attack on any future visit from the same browser (e.g., you log on to a malicious router in a coffee-shop that redirects you to an HTTP version of the page), your browser will remember it is supposed to be HTTPS only and prevent you from inadvertently giving up your information. But be careful about this, you can't change your mind and later decide part of your domain will be served over HTTP (until the 10 years have passed from when you removed this line). So plan ahead; e.g., if you believe your application may soon grow in popularity and you'll need to be on a big CDN that doesn't handle HTTPS well at a price you can afford, you may have an issue.

Also make sure you disable weak protocols. Submit your domain to an SSL Test to check for potential problems (too short key, not using TLSv1.2, using broken protocols, etc.). E.g., in nginx I use:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
share|improve this answer
    
Theoretically though, if you hit an http page, an attacker could change the redirect to an attacker controlled page, right? They can't steal the cookie, but they could still do damage. –  mikeazo Nov 16 '11 at 23:16
    
mikeazo: I agree if a user goes to example.com that's supposed to redirect to example.com an attacker can spoof my site to one that redirects to another https server which they potentially even have legitimately signed certificates for. However, this problem can't be solved; the user tried going to an unsecured site. (Even if I just disable my http version; if users still occasionally go to the http version an MITM attacker could always redirect those people.) Hopefully users would notice the different domain name or just learn to use the https version only. –  dr jimbob Nov 17 '11 at 15:12
3  
@drjimbob, part of the problem is that DJango is creating internal links - when I do an HttpResponseRedirect to a reversed view name, the end result has a http-prefix. Now, I have just coded a decorator that replaces http with https, so Django will redirect properly. Question, though - I didn't find CSRF_COOKIE_SECURE in the Django 1.3 documentation, where does it come from? Thanks. –  John C Nov 17 '11 at 19:13
1  
@Kave - Thanks. You need to set an environmental variable with a key of HTTPS to on. So if you call uwsgi from the command line via first example on django uwsgi page, you'd need to add the flag --env HTTPS=on. Or if you had an uwsgi.ini file you'd add the line: env = HTTPS=on. (The configuration is exactly identical to what you do for setting the environmental variable DJANGO_SETTINGS_MODULE. Just replace DJANGO_SETTINGS_MODULE with HTTPS and its value with on). –  dr jimbob Apr 3 '13 at 15:30
1  
It might be worthwhile to mention including the HSTS header (as suggested by @that-guy-from-over-there). More on that here: mikkel.hoegh.org/blog/2010/09/09/… –  Dolan Antenucci Jan 16 at 21:38

A common setup will have you forwarding https traffic from your webserver (i.e. Nginx) to a local http server running the Django app.

In this case it will be easier to use the SECURE_PROXY_SSL_HEADER setting (available since Django 1.4.)

https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECURE_PROXY_SSL_HEADER

share|improve this answer

Redirecting from any http:// to the corresponding https:// page is the wrong approach. Configure nginx to redirect port 80 to https://yourdomain.ext/

server {
       listen 80;
       rewrite ^/? https://$host/ permanent;
 }

or similar (check the next nginx manual near you) and do not run your application at all on port 80 (http). So, other requests on port 80 resolve to a 404 or similar (customize it, saying that your app is now secure and runs only on https with a link pointing to https://yourdomain.ext/). Then run your app only on listen port 443 (https). Using relative paths in you code is now secure, since they all resolve to the full https:// path and you avoid the http to https bouncing!

share|improve this answer
1  
Unfortunately, that's exactly how I do it - I have nginx configured to listen on 80, rewrite to https, and another server block that listens on 443 and passes thru to Django (well, uWsgi). Yet it's Django generating the http links (and I have not yet found how to change that). I don't see how they resolve to https, without going through the client browser first :( –  John C Nov 17 '11 at 13:10

I think what you're looking for is a Django middleware that will rewrite http to https. Something similar to what is addressed in this question on SO, where one answer points to this middleware. You'll probably have to write your own middleware, but it should be straightforward. (A well-focused question on SO will get you pointed in the right direction if you need help getting started.)

share|improve this answer

you should additionally send a HSTS-Header from nginx, indicating to the clients (browsers) they shall use HTTPS only

add_header Strict-Transport-Security max-age=31536000;
share|improve this answer

You need to configure django to generate either

  1. https://domain/path links with the https: scheme,
  2. //domain/path links with no scheme (the browser will interpret these as having the same scheme as the page it's currently opened to), or
  3. /path links with no scheme or domain (the browser will interpret these as having the same scheme and domain as the page it's currently pointed to).
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.