Django Best Security Practices

Django has a pretty good security record. This is due to security tools provided by Django, solid documentation on the subject of security, and a thoughtful team of core developers who are responsible to security issues.

Django has provided us with some security features that includes:

  1. Cross-site scripting (XSS) protection.
  2. Cross-site request forgery (CSRF) protection.
  3. SQL injection protection.
  4. Clickjacking protection.
  5. Support for TLS/HTTPS/HSTS, including secure cookies.
  6. Automatic HTML escaping.
  7. An expat parser hardened against XML bomb attacks.
  8. Hardened JSON, YAML, and XML serialization/deserialization tools.

TURN OFF DEBUG Mode in Production

Your production site should not be running in DEBUG mode. You should keep in mind that when you turn off DEBUG mode, you will need to set ALLOWED_HOSTS or risk raising a SuspiciousOperation error, which generates a 400 BAD REQUEST error that can be hard to debug. Attackers might find out more than they need to know about your production setup from a helpful DEBUG mode stack trace page.

KEEP YOUR SECRET KEYS SECRET

If the SECRET_KEY setting is not secret, depending on project setup, we risk an attacker gaining control of other people’s sessions, resetting passwords, and more. Our API keys and other secrets should be carefully guarded as well. These keys should not even be kept in version control. Creating environment variable may be the better options to secure them.

HTTPS Everywhere

Your entire site must be behind HTTPS. Your site’s static resources must also be served via HTTPS, otherwise visitors will get warnings about “insecure resources” which will rightly scare them away from your site. If visitors try to access your site via HTTP, they must be redirected to HTTPS. This can be done either through configuration of your web server or with Django middleware. Performance-wise, it’s better to do this at the web server level, but if you don’t have control over your web server settings for this, then redirecting via Django middleware is fine. The tool of choice for projects on Django for enforcing HTTPS/SSL across an entire site through middleware is built right in. To activate this middleware just follow these steps:

  1. Add django.middleware.security.SecurityMiddleware to the settings.MIDDLEWARE definition.
  2. Set settings.SECURE_SSL_REDIRECT to True.

Use Secure Cookies

Your site should inform the target browser to never send cookies unless it is HTTPS. For that you’ll need to set the following in your settings:

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

Use Allowed Hosts Validation

In production, you must set ALLOWED_HOSTS in your settings to a list of allowed host/domain names in order to avoid raising SuspiciousOperation exceptions. This is a security measure to prevent the use of fake HTTP host headers to submit requests.

Always Use CSRF Protection With HTTP Forms That Modify Data

Django comes with easy-to-use cross-site request forgery protection (CSRF) built in, and by default it is activated across the site via the use of middleware. Make sure that your data changing forms use the POST method, which by default will activate CSRF protection on the server side. The only valid exception to use GET method is for search forms, where it is useful for the user to have the argument visible in the URL.

Prevent Against Cross-Site Scripting (XSS) Attacks

To prevent these kind of problem, django security team has recommended this:

  1. Use format_html Over mark_safe
  2. Don’t Allow Users to Set Individual HTML Tag Attributes
  3. Use JSON Encoding for Data Consumed by JavaScript
  4. Beware Unusual JavaScript
  5. Add Content Security Policy Headers
  6. Defend Against Python Code Injection Attacks Once there were some security issues on our project. The requests coming into the site were being converted from django.http.HttpRequest objects directly into strings via creative use of the str() function, then saved to a database table. Periodically, these archived Django requests would be taken from the database and converted into Python dicts via the eval() function. This meant that arbitrary Python code could be run on the site at any time. Needless to say, upon discovery the critical security flaw was quickly removed. This just goes to show that no matter how secure Python and Django might be, we always need to be aware that certain practices are incredibly dangerous.

Python Built-Ins That Execute Code

Beware of the eval() , exec() , and execfile() built-ins. If your project allows arbitrary strings or files to be passed into any of these functions, you are leaving your system open to attack.

Don’t Use ModelForms.Meta.exclude When using ModelForms, always use Meta.fields. Never use Meta.exclude. The use of Meta.exclude is considered a grave security risk, specifically a Mass Assignment Vulnerability. We can’t stress this strongly enough. Don’t do it.

Let’s use an example to show how this mistake could be made. We’ll start with a simple ice cream store model:

# stores/models.py
from django.conf import settings
from django.db import models

class Store(models.Model):
  title = models.CharField(max_length=255)
  slug = models.SlugField()
  owner = models.ForeignKey(settings.AUTH_USER_MODEL)
  # Assume 10 more fields that cover address and contact info.

Here is the wrong way to define the ModelForm fields for this model:

from django import forms
from .models import Store
class StoreForm(forms.ModelForm):
  class Meta:
    model = Store
# DON'T DO THIS: Implicit definition of fields.
# Too easy to make mistakes!
    excludes = ("pk", "slug", "modified", "created", "owner")

In contrast, this is the right way to define the same ModelForm’s fields:

from django import forms
from .models import Store
class StoreForm(forms.ModelForm):
  class Meta:
    model = Store
    # Explicitly specifying the fields we want
    fields = (
    "title", "address_1", "address_2", "email",
    "usstate", "postal_code", "city",
    )

The first code example, as it involves less typing, appears to be the better choice. It’s not, as when you add a new model field you now you need to track the field in multiple locations (one model and one or more forms). Let’s demonstrate this in action. Perhaps after launch we decide we need to have a way of tracking store co-owners, who have all the same rights as the owner. They can access account information, change passwords, place orders, and specify banking information. The store model receives a new field as shown on the next page:

# stores/models.py
from django.conf import settings
from django.db import models
  class Store(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL)
    co_owners = models.ManyToManyField(settings.AUTH_USER_MODEL)
# Assume 10 more fields that cover address and contact info.

The first form code example which we warned against using relies on us remembering to alter it to include the new co_owners field. If we forget, then anyone accessing that store’s HTML form can add or remove co-owners. While we might remember a single form, what if we have more t han one ModelForm for a model? In complex applications this is not uncommon. On the other hand, in the second example, where we used Meta.fields we know exactly what fields each form is designed to handle. Changing the model doesn’t alter what the form exposes, and we can sleep soundly knowing that our ice cream store data is more secure.

Don’t Use ModelForms.Meta.fields = ”all

This says every model field in your model form. We advocate avoiding this technique as much as possible, as we feel that it’s simply impossible to catch all variations of input.

These are some of the Django Security Practices. Django comes with a good security record due to the diligence of its community and attention to detail. Security is one of those areas where it’s a particularly good idea to ask for help. If you find yourself confused about anything, ask questions and turn to others in the Django community for help.