One of the most loved features of Django is the built-in administration interface. It allows for an easy way to manage your data and is highly customizable. However, it is also a very common target for attackers.

In this article, we will look at some of the most common attacks against the Django administration interface and how to protect against them.

1. Use a custom URL for the administration interface

The default URL for the Django administration interface is /admin. This is a well-known fact and attackers will try to access this URL on your site.

In fact, there are bots that scan the internet for Django administration interfaces and try to log in with common usernames and passwords, so you if you use the default URL, with a very weak password, you might get hacked in a matter of minutes.

The solution is to change the URL of the administration interface. This can be done by editing the urls.py file of your project and changing the admin/ path to something else.

Let’s use the secret module to generate a random string that we can use as the new URL:

>>> import secrets
>>> secrets.token_urlsafe(16)
'J7GuncjzSMsqWhSveaYRwg'

Now, we can change the urls.py file to use this new URL:

from django.contrib import admin

urlpatterns = [
    path('J7GuncjzSMsqWhSveaYRwg/', admin.site.urls),
]

That’s good but we can make it even better.

Let’s have a different URL for the administration interface on each environment (local, staging, production). This way, we can manage from the server and rotate the URL if we suspect that it has been compromised.

Replace the urls.py file with the following:

from django.contrib import admin
from django.conf import settings
from django.urls import path

urlpatterns = [
    path(settings.ADMIN_URL, admin.site.urls),
]

Then, add the ADMIN_URL setting to the settings.py file:

ADMIN_URL = env("DJANGO_ADMIN_URL")

Finally, add the DJANGO_ADMIN_URL environment variable to the .env file:

DJANGO_ADMIN_URL=J7GuncjzSMsqWhSveaYRwg

Make sure to use a different value for each environment

2. Log login attempts to the administration interface

Knowlege is power, the more you know about the attacks against your site, the better you can protect it.

Knowing when someone is trying to log in to the administration with which credentials can help you take action before it’s too late.

As an example have a look at the following screenshot:

Login attempts to the administration interface

This is a screenshot from a site that I manage. As you can see, there are a lot of login attempts to the administration (more than 400 in a few days), and one username was used by my team.

But how can we log login attempts to the administration interface?

The solution is to use a Django middleware that logs all requests to the administration interface or even the django-admin-honeypot package.

Let’s use the django-admin-honeypot package to log login attempts to the administration interface.

Install the package:

pip install django-admin-honeypot

Add the admin_honeypot app to the INSTALLED_APPS setting:

INSTALLED_APPS = [
    # ...
    "admin_honeypot",
]

Add the url to the urls.py file:

from django.contrib import admin

urlpatterns = [
    path('J7GuncjzSMsqWhSveaYRwg/', admin.site.urls),
    path('admin/', include('admin_honeypot.urls', namespace='admin_honeypot')), # Fake admin url for honeypot
]

Finally, add the ADMIN_HONEYPOT_EMAIL_ADMINS setting to the settings.py file:

ADMIN_HONEYPOT_EMAIL_ADMINS = True

Note that the django-admin-honeypot package will send an email to the ADMINS setting when someone tries to log in to the administration interface, you can disable this by setting the ADMIN_HONEYPOT_EMAIL_ADMINS setting to False.

Now you can access the administration interface using the fake URL: http://localhost:8000/admin/ and you will see the login attempts with the username and password used in plain text and the IP address of the attacker.

Please note that IP might note show up if you are using a reverse proxy like Nginx or Apache, in this case, you need to configure the proxy to pass the IP address to Django.

Note: django-admin-honeypot is not supported on Django 4.0 yet, you can follow the issue here.

3. Identify your environment

To avoid embarrassing situations, you need to know in a glance which environment you are working on.

One simple solution that does not require any extra packages or HTML/CSS modifications is to add a different administration header for each environment.

A simple implementation would be to add the following code to the your main urls.py file:

from django.contrib import admin
from django.conf import settings
from django.urls import path

.... your code here ...


if settings.DEBUG:
    admin.site.site_header = "Django - LOCAL 🚧"
else:
    admin.site.site_header = "🚨 Django - PRODUCTION 🚨"

4. Block other IP addresses from accessing the administration interface

If you are the only one who needs to access the administration interface, you can block all other IP addresses from accessing it, keep in mind that this it is still possible to spoof* the IP address but will add an extra layer of security.

Let’s keep the Django philosophy not reinventing the wheel and use the django-ipware package to get the IP address of the client.

Install the package:

pip install django-ipware

Create a new middleware file middleware.py in the core app:

import logging

from django.conf import settings
from django.shortcuts import redirect, reverse
from ipware import get_client_ip

logger = logging.getLogger(__name__)


class IPAdminRestrictMiddleware:
    """
    Middleware to restrict access to the admin panel by IP address.
    """

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.path.startswith(f"/{settings.ADMIN_URL}"):
            print("Here we are")
            client_ip, is_routable = get_client_ip(request)
            print(f"Client IP: {client_ip}")
            if client_ip not in settings.WHITELISTED_IPS:
                logger.info(
                    "A user tried to access the admin panel from an unauthorized IP address."
                )
                return redirect("base")
        response = self.get_response(request)
        return response

Then, add the middleware to the MIDDLEWARE setting:

MIDDLEWARE = [
    # ...
    "core.middleware.IPAdminRestrictMiddleware",
]

Finally, add the WHITELISTED_IPS setting to the settings.py file:

WHITELISTED_IPS = env.list("WHITELISTED_IPS", default=["127.0.0.1"])

That way, you can add IP in your environment variables and they will be used as the whitelist.

For extra security, please read my article on setting up a Firewall.

*Spoofing: Spoofing is when someone or something pretends to be something else in order to gain an advantage. In our context, spoofing can be used to bypass the administration IP address restriction.

5. Run the deployment checklist before going to production

Django has a built-in deployment checklist that you can run before going to production. Django includes many security features. Some are built-in and always enabled. Others are optional because they aren’t always appropriate

To run the checklist, use the following command:

python manage.py check --deploy

If you run this command in your local enviroment, you will get a lot of warnings, but you are most likely using a different configuration for your local environment than your production environment so you can use the --settings option to specify the settings file to use:

python manage.py check --deploy --settings=config.settings.production

On my personal site, I get the following warnings:

System check identified some issues:

WARNINGS:
?: (security.W009) Your SECRET_KEY has less than 50 characters, less than 5 unique characters, or it's prefixed with 'django-insecure-' indicating that it was generated automatically by Django. Please generate a long and random value, otherwise many of Django's security-critical features will be vulnerable to attack.

This warning can be safely ignored because I am using the local settings file and the SECRET_KEY is not used in production.

6. Use a two-factor authentication

The final step to secure your administration interface is to use a two-factor authentication. This way, even if someone gets your username and password, byphases the IP address restriction, and guesses the URL of the administration interface, they will not be able to log in without the second factor.

There are many packages that you can use to add two-factor authentication to your Django site, but I recommend using the django-two-factor-auth package, it is well maintained and has a lot of features.

Install the package:

pip install django-two-factor-auth

Add the two_factor app to the INSTALLED_APPS setting:

INSTALLED_APPS = [
    # ...
    'django_otp',
    'django_otp.plugins.otp_static',
    'django_otp.plugins.otp_totp',
    'django_otp.plugins.otp_email',  # <- if you want email capability.
    'two_factor',
    'two_factor.plugins.email',  # <- if you want email capability.
    'two_factor.plugins.yubikey',  # <- for yubikey capability.
]

Add the django-otp middleware to your MIDDLEWARE. Make sure it comes after AuthenticationMiddleware:

MIDDLEWARE = [
    # ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'two_factor.middleware.threadlocals.ThreadLocals',
    # ...
]

Point to the new login pages in your settings.py:

LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = 'two_factor:profile'

Add the routes to your project url configuration:

urlpatterns = [
    # ...
    path('account/', include('two_factor.urls', 'two_factor')),
    # ...
]

By default the admin login is patched to use the login views provided by this application. Patching the admin is required as users would otherwise be able to circumvent OTP verification

If you want to enforce two factor authentication in the admin and use the default admin site, you can add the following to your admin.py file:

from django.contrib import admin
from two_factor.admin import AdminSiteOTPRequired

admin.site.__class__ = AdminSiteOTPRequired

urlpatterns = [
    path('admin/', admin.site.urls),
    ...
]

Finally, run the migrations:

python manage.py migrate

Now, you can access the administration interface using the fake URL: http://localhost:8000/admin/ and you will be asked to enable two-factor authentication.

Conclusion

I hope that this article helped you secure your Django administration interface, I use all the techniques mentioned in this article on my personal site and professional projects, and while they are not perfect, they will help you secure your site and sleep better at night.


I hope this article was helpful, if you have any question or suggestion, feel free to reach out to me on :

You can use my articles with no cost or attribution, feel free to edit them and make them your own.