How to enhance the security of your django projects
Applications, settings and tools to enhance the security of your django applications.
It’s reassuring to know that your web application is secure, and django provides a well tested framework with a reputation for security. But there may be some settings available, which if enabled, can provide a more secure application.
django provides a great checklist which covers the basics when deploying your project, but I’ll cover further settings here. The important thing to take from this is the management command to check your project setup;
python manage.py check --deploy
1. django admin
There are some things to consider when it comes to security in your django application. A simple first one is the location of your django admin. By default this will use the path;
path('admin/', admin.site.urls),
But for a “quick win” you could hide that on another path that is only known to those who need to access it.
If you move your admin, you might also consider setting up django-admin-honeypot which you can use to set up a fake admin login on the default path which will log/notify you about attempted logins.
2. Two factor authentication
When it comes to administrator access it’s almost essential to have two factor authentication. And this is really easy to do in django with apps like django-otp or django-two-factor-auth which is built on top of it. These apps are installed by patching the admin site which changes the login form to include the one time password (OTP) field and ensures that users are using an OTP device via middleware. If you implement two factor authentication, you may wish to consider disabling the CMS toolbar for anonymous users. This is done by setting CMS_TOOLBAR_ANONYMOUS_ON = False
and you’d do this because the login form in the toolbar would not contain the OTP field required to authenticate with the admin site.
3. Password validation
Password validation is another area where you can quite easily improve on the strong defaults provided by django. The default password validation django provides will help to prevent weak passwords and even perform checks against the attributes of a user to ensure the password isn’t similar to their name/email/username. django-zxcvbn-password brings some of that password validation to the frontend with a strength indicator and a "crack time" while allowed extension of the backend including custom word lists.
4. Permissions policy
Previously known as 'Feature Policy' this is a draft HTTP header which enables you to allow or block browser features. This header can be introduced to your application using django-permissions-policy which uses middleware and settings to add the header to enable the features your application makes use of.
5. Referrer policy
Starting from django 3.0 support is built-in for this header via the SecurityMiddleware
but if you’re still using 2.2 then the third party app django-http-referrer-policy can also apply this header.
6. django settings
You may have heard the term "simple is better than complex" and this is no different in your settings. I often see projects using multiple settings file under a settings module, with a base settings file then environment specific files which import the base and are then set as DJANGO_SETTINGS_MODULE
in the respective environments.
I used this approach for many years, but eventually it became clear that it was really complex and hard to maintain. Ultimately what you want is simple, local development is likely in debug mode and HTTP whereas anything running on a server is HTTPS and debug mode is turned off. With this in mind I’ll now only run a traditional settings file, and environment variables control everything.
Managing environment variables can be a little laborious, but there are some tools to help. environs can read key/value pairs from a file (.env
by default) and set them as environment variables. This tool can be integrated into your workflow and imported into python, for example manage.py
might include;
from environs import Env
env = Env()
env.read_env() # read .env file, if it exists
It also provides type casting and validation which makes this a really powerful tool for creating your settings file;
# casting
max_connections = env.int("MAX_CONNECTIONS") # => 100
log_level = env.log_level("LOG_LEVEL") # => logging.DEBUG
env.str(
"NODE_ENV",
validate=OneOf(
["production", "development"], error="NODE_ENV must be one of: {choices}"
),
)
# => Environment variable "NODE_ENV" invalid: ['NODE_ENV must be one of: production, development']
env.str("EMAIL", validate=[Length(min=4), Email()])
Starting with settings from django itself, not django-cms, first it’s important to ensure you include the following middleware classes;
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
These settings will help to improve the security of your application, but often they’re disabled by default.
ALLOWED_HOSTS (docs)
By default this is an empty list, however if set to a list of strings representing the domain names which the django application can serve, this adds a security measure to prevent HTTP Host header attacks. If a request hits django which doesn’t match a hostname from this list, a SuspiciousOperation
will be raised.
SESSION_COOKIE_SECURE (docs)
Disabled by default, once enabled it’ll be marked as "secure" meaning browsers should ensure the cookie is sent via HTTPS connection.
CSRF_COOKIE_SECURE (docs)
Disabled by default, once enabled it’ll be marked as "secure" meaning browsers should ensure the cookie is sent via HTTPS connection.
SECURE_SSL_REDIRECT (docs)
Disabled by default, once enabled the SecurityMiddleware
will redirect all non-HTTPS traffic to HTTPS, unless it’s a URL matching a regular expression listed in SECURE_REDIRECT_EXEMPT
.
CSRF_COOKIE_HTTPONLY (docs)
Disabled by default, and one to use with caution but one to consider enabling if the CSRF token is not required by javascript. When enabled the CSRF cookie client-side Javascript will not be able to access the CSRF cookie.
X_FRAME_OPTIONS (docs)
Defaults to 'DENY', this provides the value for the X-Frame-Options header added by the XFrameOptionsMiddleware
.
The following settings relate to the HTTP Strict Transport Security header which can instruct modern browsers to not connect to a domain via an insecure connection.
SECURE_HSTS_SECONDS (docs)
The default value is 0 however if set to a positive integer value this time will be used to set the "Strict-Transport-Security" header.
SECURE_HSTS_PRELOAD (docs)
Disabled by default, once enabled SecurityMiddleware
adds the preload
directive to the HSTS header.
SECURE_HSTS_INCLUDE_SUBDOMAINS (docs)
Disabled by default, once enabled SecurityMiddleare
adds the includeSubDomains
directive to the HSTS header.
You can read further details on HSTS in the django docs here.
7. django-cms settings
CMS_TOOLBAR_ANONYMOUS_ON (docs)
Enabled by default, so an anonymous user who appends ?edit
to the URL will be shown the CMS toolbar.
This is one to consider disabling, because even though it provides a way to login without visiting the admin, it may be seen by users who don’t need to see it. Or perhaps, if you’re using frontend caching like varnish, the toolbar may get cached and be displayed to all users regardless of whether they have ?edit
on their URL.
CMS_INTERNAL_IPS (docs)
By default this is an empty list, but if set the toolbar will only appear for client IP addresses that are in the list. IP addresses are obtained via the next setting.
CMS_REQUEST_IP_RESOLVER (docs)
If the default method for extracting a client IP address doesn’t work for your project you can define a custom method with this setting. The method should accept a single argument, request, and return an IP address as a string.
CMS_DEFAULT_X_FRAME_OPTIONS (docs)
This setting allows for the implementation of django’s X_FRAME_OPTIONS
setting on CMS pages. By default pages will inherit the setting, but this does require that the root page(s) or home page do define a setting which can be found in the advanced page settings.
8. Helpful tools
The following are tools which can help you get a better understanding of the security offered by your site.
securityheaders.com
This service will scan the HTTP response headers and provide a rating for the result. This is a really easy way to get an idea of your site’s setup and it provides useful information about the headers that are sent along with any missing headers.
DJ Checkup
This service is a django focused tool to run some basic checks on headers and cookies.
HSTS preload
Checks the status and eligibility of your site for HSTS preload and provides a way to submit it to Chrome’s HSTS preload list.
About the author: Mark is the Tech Lead for the django CMS Association. He lives in the UK and has been an active contributor to the django CMS ecosystem since 2017. He has been the lead on many django CMS projects, including London Marathon Events and World Marathon Majors. Mark is a maintainer and contributor to django-bleach, django-sql-explorer, djangoproject.com, DjangoGirls.org, and he once achieved a penetration test which reported zero findings or recommendations, which people struggled to comprehend.
blog comments powered by Disqus