Django has a number of built in security measures which are really helpful. In fact as frameworks go by default it does a lot of really smart things security wise. There are a few less commonly known settings that improve security even more which I’ll share below. This information is up to date as of Django 1.11 / 2.1.
Securing Django is part server configuration, part Django settings, and part not being a fool:
There’s an old Russian proverb “Simplicity is worse than thievery” that loosely translates to: “A well meaning fool can do more damage than an enemy or a criminal would intentionally”.
To avoid being a well meaning fool when it comes to Django, start by reading Django’s official documentation on security. Django handles the following out of the box:
- Cross site scripting (XSS) attacks. By default the Django template engine scrubs all content that gets outputted to the client for script tags and other potentially malicious data. You have to mark content ‘safe’ for it to be rendered without the sanitization step.
- Cross site forgery request (CSFR). By default Django will prevent replaying form posts which ensures data security.
- SQL Injection. Django’s ORM will sanitize all query variables so you don’t have to think about it. WARNING: if you are writing custom queries (which may fall under the category of ‘fool’) then you need to take extra precautions yourself.
- Clickjack protection. Django will set the
- HTTPS / SSL. Django has a solid understand of SSL and I’ll list the settings I use in combination with SSL. Require SSL from day one on your project and you’ll thank yourself later.
- Host header validation. Django has an ALLOWED_HOSTS setting so you can ensure the URL you want users to see in their browser’s address bar is the only valid way to access the site (and not by IP, some stray domain name, or a subdomain).
- Built In Password Hashing. Django includes a User model and a related Permissions system. When using this part of Django, by default passwords are stored securely in the database (via configurable hashing logic). This means only your users know their passwords. The database stores the hash of the password (a one way encrypted version of it). The design allows for flexibility and easy upgrades to future hashing functions.
How To Secure Django – Configuration Steps:
- Enable SSL, and redirect all non-SSL requests to SSL on the web server level.
- Run a firewall so only ports 80 and 443 (SSL) is open to the world. The only purpose of port 80 is to redirect to 443. The database port, memcache, etc should be locked down and non-accessible to the outside world. SSH (port 22) should only be accessible to trusted IPs or better yet through a VPN. For goodness sake – shut off FTP!
- Don’t host the admin site at /admin, setup a custom URL. Yes this is security through obscurity, but it will thwart all the script kiddies scanning for /admin.
# In urls.py set a custom URL for the admin url(r'^make-up-your-own-secret-admin-path/', admin.site.urls),
- In my live settings.py file I have the following. The full list of Django settings is here.
DEBUG = False # required for live environments SECRET_KEY = '...' # to get a new value try this generator # lock down allowed hosts to just the domains I'm okay with ALLOWED_HOSTS = ['mydomain.com', 'www.mydomain.com'] # security settings CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 3600 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_SSL_HOST = 'www.mydomain.com' SECURE_SSL_REDIRECT = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_BROWSER_XSS_FILTER = True X_FRAME_OPTIONS = 'SAMEORIGIN' # see also # SECURE_HSTS_PRELOAD # CSRF_USE_SESSIONS # CSRF_FAILURE_VIEW # only available in Django 2.1+ # SESSION_COOKIE_SAMESITE # CSRF_COOKIE_SAMESITE
- Many of the options above rely on apps / middleware being enabled, here is an abridged version of the associated configuration:
INSTALLED_APPS = ( 'django.contrib.admin.apps.SimpleAdminConfig', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', 'django.contrib.sitemaps', 'django.contrib.redirects', ... ) MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', ... ]
- In your database connections, setup a separate user for migrations that has ALL permissions and use a less privileged user for general web requests.
Useful tools for verifying the security of your Django app:
- I have 46 dependencies in my largest Django project’s requirements.txt file! That is a lot of ghetto software to worry about 😉 I use a tool called safety to check the project’s dependencies for known vulnerabilities like so:
$ safety check -r requirements.txt
- Once your site is live, Sasha’s Pony Checkup will scan your Django app for common vulnerabilities and misconfigurations.
- I find this SSL certificate checker tool useful.
Areas to be careful of:
- User Uploads are a potential area of concern. Anytime you are allowing someone on the internet to upload files to your server a door the size of a castle gate is opened for potential hacking. Unfortunately Django doesn’t have a lot of built in things to assist with this. So you need to put in your own control mechanisms such as:
- Moderation – it would be a bad idea to let User A upload something and let User B see it without an admin checking it first.
- Validate uploaded files for size, file extension (which tells you squat but blocks common user mishaps), file MIME type, etc.
- Don’t allow uploads to go into a folder that can be executed by your web server via a web request (think .py files, .php files, etc).
- Storing passwords / secrets related to the production environment in the application config file is convenient, but they shouldn’t be in source control. The options are 1) setup a config file on the server the settings file reads 2) use environment variables, this stackoverflow page has examples.
- Make sure to filter out sensitive data from the logs. Django apps are commonly configured to email the admins when a 500 error is thrown. That email will contain all details of the request including GET and POST parameters entered by the user. To protect against exposing sensitive data in the logs Django has built in decorators sensitive_variables() and sensitive_post_parameters(). Consider a login form that does a POST with fields ‘username’ and ‘password’. If an exception fires during the request that bubbles up causing a 500 error, these decorators will ensure the password value appears as ***** in the log output.
# example in a standard view @sensitive_post_parameters('password') def login_view(request): ... # example in a class based view class LogininView(View): ... @method_decorator(sensitive_post_parameters('password')) def post(self, request, *args, **kwargs): ...
Note that if you are doing your own logging statements then scrubbing for sensitive data is still up to you.
- Setup two factor authentication using a library such as django-otp or django-two-factor-auth.
- Setup CORS headers (required if you are serving fonts from a different domain or using a CDN). I’ve had success with the django-cors-headers library.
- For more ideas check out the Django Packages -> Security section.
Did I miss anything? If so please let me know in the comments below or through my contact page and I’ll update!