Django CSRF "Referer Malformed"... but it isn't

429 Views Asked by At

I'm trying to test a deployment config for a Django setup that works fine in development mode.

I have name-based routing via Nginx's ssl_preread module on a load balancer, and SSL terminates at another Nginx instance on the server itself where the requests are proxied to uwsgi by socket.

server {
    server_name dev.domain.net;
    listen 80 proxy_protocol;
    listen [::]:80 proxy_protocol;
        location / {
            return 301 https://$host$request_uri;
        }
}
server {
    server_name dev.domain.net;
    listen 443 ssl;
    listen [::]:443 ssl;
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/run/uwsgi/website.sock;
    }
    location /favicon.ico {
        access_log off; log_not_found off;
    }
}

I have uwsgi set to log %(host) and %(referer), they match in the logs.

In my uwsgi_params I'm passing $host and $referer like so, since I'm using name-based routing I pick up the $server_name variable that triggered the Nginx response...

uwsgi_param  HTTP_REFERER       $server_name;
uwsgi_param  HTTP_HOST          $host;

Adding (or taking away) protocols and ports to these makes no difference. Taking them away predictably generates a Django ALLOWED_HOSTS debug error.

I've confirmed that my ALLOWED_HOSTS includes the $host. I've tried adding CSRF_TRUSTED_ORIGINS for the same $host variable. I've tried setting CSRF_COOKIE_DOMAIN for the same $host variable. I have CSRF_COOKIE_SECURE set to True per the docs recommendation.

No matter what combination of the above settings are used, I get:

Referer checking failed - Referer is malformed. on all POST requests.

1

There are 1 best solutions below

0
RNC On

Short answer: don't use the uwsgi unix socket, but rather use http-socket and send the proxy request to localhost over unencrypted http (in uwsgi ini file):

http-socket = 127.0.0.1:8001

In nginx, get rid of uwsgi proxy params and simply proxy_pass with proxy_protocol headers enabled:

server {
    server_name dev.domain.net;
    listen 443 ssl proxy_protocol;
    listen [::]:443 ssl proxy_protocol;
    location / {
        proxy_pass http://127.0.0.1:8001;
    }
    location /favicon.ico {
        access_log off; log_not_found off;
    }
}

At that point you can enable all of the recommended deployment settings in the Django docs, explicitly declare your ALLOWED_HOSTS and everything works fine.

These are a quite silly series of hoops with no apparent correct set of answers, especially considering referers are client headers that are easily forged.

The better answer is Django needs to get rid of a client referer check in its CSRF mechanism, it's pointless and makes no sense...