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:
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 :
- By email at [email protected].
- If you want to know more about me, you can check out my about page.
You can use my articles with no cost or attribution, feel free to edit them and make them your own.