Writeup of the DjangoCon Europe 2019 talk »Django and Web Security Headers« by Adam Johnson
Adam Johnson: I started programming when I was eight years old in good ol’ Quickbasic and have been hooked since. I’ve worked with Django since 2012, and have been a Django core contributor since 2016.
The web is a platform with a lot of backwards compatibility concerns and all browsers are trying to keep all the websites working for all time, be they modern HTTP2, or ancient HTTP. Since old stuff hangs around, the one thing that is okay to add and upgrade, are http headers.
There is a page to check your headers, and also the Mozilla observatory – check them out in any case, since you'll learn something about your page.
XSS stands for Cross-Site-Scripting (including code from different domains, mostly referring to malicious usage). Most browsers have XSS auditors on by default. They are not entirely backwards compatible, but they'll stop that one request.
If you set the mode to
block via your header, though, the XSS auditor will block the entire page.
In Django: set
SECURE_BROWSER_XSS_FILTER = True and include the
SecurityMiddleware in your middlewares.
You should serve your site over HTTPS. Most websites are HTTPS, but problems can occur if your browser asks for HTTP first, since the request/redirect before the upgrade will be insecure. This header tells your browser to never use HTTP with that page ever again (within the limits of the timeout). There is also a huge list of domains with this enabled, to prevent any HTTP requests going out.
In Django: set
SECURE_HSTS_SECONDS to the timeout if the
SecurityMiddleware is active. Please ramp up the
seconds gradually – only enable
preload when you're really sure. If you break your HTTPS
after having set this flag, any browser that has seen is will not be able to see your website again.
Browsers guess content type with "MIME Sniffing". This backfires when user uploaded images are interpreted as HTML. You
can opt out by sending the header value
SECURE_CONTENT_TYPE_NOSNIFF = True.
Clickjacking embeds another webpage as an iframe, and the header tells browser to never embed the page (or only from okay pages).
X_FRAME_OPTIONS = 'DENY' (or use whitelisting), and include the
Browsers pass the
Referer (with typo) with every request, telling the page where they came from. But this leaks
information – both personal information, and metadata. This header controls who gets this information.
In Django: Install
django-referrer-policy, add its
ReferrerPolicyMiddleware, and set
'same-origin' or other values.
This header stops your page from including content from any site. It's the strongest way to prevent XSS, clickjacking, and others. It's a huge complex topic. The header contains ; separated directives, like these:
Read about strict mode here, or consider starting with reporting mode to help with the roll-out in existing pages.
default-src 'self'; # by default only from this domain img-src *; # images from anywhere script-src userscripts.example.com # scripts only from here
In Django: Mozilla provides
CSPMiddleware, and then set a couple of values, e.g.
CSP_DEFAULT_SRC = 'self'.
New and not yet supported in all browsers – this header lets you disable browser feature, like autoplay, geolocation, camera, etc.
In Django: Install
FeaturePolicyMiddleware and set