At some point in your Django hosting journey you are bound to encounter the Django allowed_hosts question. In this post we will look the very basics of why we need the allowed_hosts setting and how it helps you.

Some allowed_hosts background

Django both provides and uses functions to create fully qualified URLs based on the incoming web request. 

Depending on the server configuration, Django makes use of the host header that is sent by the browser on the request to create the fully qualified URL.

The host header is arbitrary and sent on behalf of the user by the browser. Typically, the host header is used when using Apache virtual hosts or Nginx server blocks to determine which application should handle the request where many applications could be sharing the same server IP address. 

An issue arises where the host header is used without being checked leading to a potential risk of page resources being loaded from an entirely different domain. For example, a request could be structured to include an malicious host header.

GET /index.html HTTP/1.1
Host: evildomain.com

The application itself doesn't know about the environment so whilst it might be the only application running, it still makes use of the host header to know if it should handle the request.

The potentially malicious host header could then rendered back to the user to sideload from a malicious site. 

<script src="{{ request.META.HTTP_HOST }}/evilscript.js">

By itself, this is limited to the person requesting the file but could then be used to pass on the now malicious page through other attack vectors.

A common misunderstanding is that CORS (cross-origin resource sharing) would provide some protection in this scenario but since the link is static and not loaded through an XMLHTTPRequest, CORS has no bearing upon it.

Web cache poisoning

Once the user has requested the file, a caching proxy or a CDN in front of the site could then store a copy of the page which would now include the malicious request. In turn, users requesting the same piece of content would be served up the poisoned and cached copy of the content.

Password reset poisoning

If the web cache is poisioned, it then becomes possible for the malicious domain to intercept legitimate traffic. Typically, a password reset link mail will include a one-time token and a link back to the originating site for the actual reset to take place. The malicious link could then be used to grab the token, alert the attacker who could then perform the reset on behalf of the user on the legitimate site.

https://{{ request.META.HTTP_HOST }}/password-reset?token=<secret token>&email=victim@friendlydomain.com

Allowed hosts to the rescue

Django addresses this through the get_host() method of django.http.HttpRequest. This method validates the requested host header against the hosts listed in the ALLOWED_HOSTS settings. If the host does not match then a SuspiciousOperation exception will be thrown. 

Note that if your code uses the host header from request.META directly then ALLOWED_HOSTS will not be checked and you will not have any validation.

How does Divio handle allowed hosts ?

Projects created through the Divio Control Panel make use of the aldryn-django package which includes opinionated settings and configuration settings.

One such configuration change is the automatic setting of allowed_hosts which is based upon your configuration in Divio Control Panel. Changes made to domains, aliases or re-directs will be used to update the allowed_hosts setting. 

Note that you must still always check and ensure your allowed_hosts setting is up-to-date and correct and that your application is kept secure for your users.

If you haven't already, get started with a free account and explore some Divio projects.