Security Through Obscurity: The Django Admin Interface

Monday, May 19th, 2014

http://jw.pe/admin/. Try navigating to it.

If I've done things right, you should have seen a message about evil wizards, instead of the customary admin login page. This is good. Let's talk about why.

Bad People, Hackers, and dictionary attacks

If you run a Django site, and regularly make changes to existing models behind the scenes, you're probably accustomed to visiting the /admin/ URL from time to time. This is the default base URL for the Django administration interface. Most people (including myself, for a long time) are content to leave this URL untouched in their settings. However, take a step back and think about this for a second. If I was a Bad Person, or god forbid, a Hacker, knowing that I could navigate to /admin/ and be one login page away from having interactive access to your database models would make my life a lot easier.

Don't panic. Before you get scared, remember that online dictionary attacks are (relatively) slow and produce a spike in traffic that anyone who actively monitors their site would be bound to notice. Additionally, they are fairly easy to mitigate through rate-limiting requests to your admin URL. Check out django-axes for an implementation of this.

However, why make things easy by showing an attacker the door so that they can try to kick it down? We can easily add an additional layer of security by obfuscating the admin URL.

Keep it secret, keep it safe

In order to make our admin URL secure and configurable, we are going to follow the philosophy of the twelve-factor app and make our URL path an environment variable. This will let us change the location of the admin interface for different deploys, or if we believe one URL has been compromised. It will also allow us to publicly host our app code without revealing the location of our admin.

We should use a random, hard-to-guess string for our admin URL path. It's easy to generate one of these using the uuid [module]. Make sure to use the uuid4 method as this makes a random UUID, as opposed to the other methods which base the UUID on various external factors.

First, we pull our ADMIN_URL_PATH into settings.py using the os module:

We can then reference this path in our base urlpatterns:

Now all of our admin URLs will be prefixed by a random, difficult to guess string. This immediately makes any attack on our admin interface much harder.

The honey on the cake

All this is well and good - our admin interface is happily hidden and only we know where it is. However, maybe we want to go a step further and find out which Bad People are trying to guess their way into our admin. We can use a Django package named django-admin-honeypot to set up a fake /admin/ endpoint, which logs attempted access to the interface and notifies the real site admins of the attempt. Fun.


Know of any other quick Django security wins? Let me know in the comments. If you liked this post, you should probably follow me on Twitter.

comments powered by Disqus