Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Django's CSRF work over HTTPS?

I have a Django website at http://example.com that works fine, including post requests. I've added HTTPS so my site is accessible at https://example.com too.

I can load any page on HTTPS, but I always get CSRF validation errors when I try to POST. POST requests work fine on HTTP.

My Django process is running with gunicorn behind nginx, and I have nginx setting X_Forwarded_For. So, an HTTPS request has the following headers (taken from request.META):

'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com:80',
'HTTP_REFERER': 'https://example.com/user/delete/49/',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'https',
'HTTP_X_REAL_IP': '1.2.3.4',

and an HTTP request has the following headers:

'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com.com:80',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'http',
'HTTP_X_REAL_IP': '1.2.3.4',

Why doesn't CSRF work on HTTPS when I have no problems over HTTP?

like image 831
Wilfred Hughes Avatar asked Oct 25 '13 14:10

Wilfred Hughes


People also ask

Where is the CSRF middleware in Django?

The CSRF middleware is activated by default in the MIDDLEWARE setting. If you override that setting, remember that 'django.middleware.csrf.CsrfViewMiddleware' should come before any view middleware that assume that CSRF attacks have been dealt with.

How to get the csrf token cookie name?

The CSRF token cookie is named csrftoken by default, but you can control the cookie name via the CSRF_COOKIE_NAME setting. The above code could be simplified by using the JavaScript Cookie library to replace getCookie: The CSRF token is also present in the DOM in a masked form, but only if explicitly included using csrf_token in a template.

What is the best way to protect a view from CSRF attacks?

Rather than adding CsrfViewMiddleware as a blanket protection, you can use the csrf_protect () decorator, which has exactly the same functionality, on particular views that need the protection. It must be used both on views that insert the CSRF token in the output, and on those that accept the POST form data.


2 Answers

Turns out it was an nginx configuration issue. My server setup is:

nginx -> nginx -> gunicorn

On the second nginx system, I had

proxy_set_header        Host            $host:$server_port;

However, since HTTPS is terminated at the first nginx, $server_port was always 80.

On HTTPS, Django does strict referer checking (see point 4). Looking at the Django source:

good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
    reason = REASON_BAD_REFERER % (referer, good_referer)
    logger.warning('Forbidden (%s): %s', reason, request.path,
        extra={
            'status_code': 403,
            'request': request,
        }
    )
    return self._reject(request, reason)

CSRF validation was failing because "https://example.com/" != "https://example.com:80/".

like image 142
Wilfred Hughes Avatar answered Oct 15 '22 05:10

Wilfred Hughes


Like mentioned here I solved this problem by adding the following Django constants in settings.py so Django considers proxy headers:

# Setup support for proxy headers
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
like image 40
Florian Sager Avatar answered Oct 15 '22 07:10

Florian Sager