Django est un excellent framework doté de puissantes fonctionnalités de sécurité intégrées. Cependant, de nombreux développeurs, en particulier au début des projets, commettent l'erreur de laisser l'URL /admin telle quelle, en suivant les documents officiels ou les tutoriels.

C'est comme si on affichait une grande pancarte "Voici la porte d'entrée de notre maison". Les scanners automatisés et les bots d'attaque du monde entier commencent toujours par scanner example.com/admin/ une fois qu'ils découvrent un site web.

Dans cet article, nous allons explorer quelques méthodes clés pour protéger en toute sécurité la page d'administration Django, depuis un simple changement d'URL jusqu'à la mise en place d'une défense active bloquant immédiatement les IP des intrus. Nous nous concentrerons sur les raisons pour lesquelles cela doit être fait.


1. La base : Changer l'URL de l'administration (utiliser des variables d'environnement)



C'est la première ligne de défense la plus facile, la plus rapide et la plus efficace.

🤔 WHY : Pourquoi cacher l'URL ?

Un attaquant ne peut pas tenter une attaque par force brute ou voler des identifiants sans connaître l'URL. Au lieu d'utiliser un chemin connu de tous comme /admin, utilisez un chemin imprévisible comme my-super-secret-admin-path/, ce qui peut bloquer 99 % des attaques automatisées.

Bien que 'cacher' (Obscurity) ne représente pas la totalité de la 'sécurité' (Security), c'est un moyen de défense ayant le meilleur rapport coût-efficacité.

🚀 HOW : Injection via des variables d'environnement

Il est préférable de ne pas coder l'URL en dur dans le code, mais de l'injecter via une variable d'environnement (Environment Variable).

  1. Fichier .env (ou configuration des variables d'environnement du serveur)
# .env
# Utilisez une chaîne complexe que personne ne peut deviner.
DJANGO_ADMIN_URL=my-secret-admin-portal-b7x9z/
  1. settings.py
# settings.py
import os

# Définir une valeur par défaut mais lire à partir de la variable d'environnement
ADMIN_URL = os.environ.get('DJANGO_ADMIN_URL', 'admin/')
  1. urls.py (projet principal)
# urls.py
from django.contrib import admin
from django.urls import path
from django.conf import settings # importer le module settings

urlpatterns = [
    # utiliser la valeur de settings.ADMIN_URL au lieu de admin/
    path(settings.ADMIN_URL, admin.site.urls),
    # ... autres urls
]

Maintenant, même si l'URL admin/ est utilisée dans l'environnement de développement, il suffit de changer la variable d'environnement pour cacher le véritable chemin d'accès à l'administration sur le serveur de production.


2. Élever les murs : Limiter l'accès par IP dans Nginx

Si jamais l'URL est exposée, ces restrictions peuvent bloquer l'accès à la page d'administration si l'IP n'est pas autorisée, ce qui constitue une méthode très efficace.

🤔 WHY : Pourquoi devrions-nous bloquer dans Nginx ?

Cette méthode bloque le trafic d'attaque avant qu'il atteigne Django (l'application) en le stoppant au niveau du serveur web (Nginx). Cela signifie que Django ne saura même pas qu'une attaque a été tentée, ce qui évite le gaspillage de ressources inutile. C'est la solution la plus sûre si un administrateur n'accède que depuis des IP spécifiques (bureau, VPN, etc.).

HOW : Exemple de configuration Nginx

Ajoutez un bloc location dans le fichier de configuration Nginx (sites-available pour le site concerné).

server {
    # ... (configuration existante)

    # Spécifiez le même chemin que la variable d'environnement ADMIN_URL
    location /my-secret-admin-portal-b7x9z/ {
        # 1. Adresse IP autorisée (ex : IP fixe du bureau)
        allow 192.168.0.10;
        # 2. Plage d'adresses IP autorisées (ex : plage VPN)
        allow 10.0.0.0/24;
        # 3. Hôte local (pour l'interne du serveur)
        allow 127.0.0.1;

        # 4. Bloquer tout accès autre que les IP spécifiées ci-dessus
        deny all;

        # 5. Transférer toute la gestion à uwsgi/gunicorn
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }

    # ... (autres configurations location)
}

Désormais, tout utilisateur dont l'IP n'est pas autorisée se verra interdire l'accès à cette URL par Django sans réponse, et Nginx renverra immédiatement une erreur 403 Forbidden.


3. Le Gardien : Limiter le nombre de tentatives de connexion (django-axes)



Si un attaquant réussit à découvrir l'URL et contourne la limite IP, il est maintenant temps de contrer les attaques par force brute.

🤔 WHY : Pourquoi limiter le nombre d'essais ?

Une attaque par force brute soumet automatiquement des milliers, voire des dizaines de milliers de mots de passe pour un identifiant de compte aussi courant que 'admin'. Un paquet comme django-axes crée des règles telles que "Bloquer l'IP ou le compte après 5 échecs de connexion en peu de temps".

Cela rend quasiment inutilisables les scripts automatisés.

HOW : Utiliser django-axes

django-axes est le paquet le plus standard pour cette tâche.

  1. Installer : pip install django-axes

  2. Enregistrement dans settings.py :

INSTALLED_APPS = [
    # ...
    'axes', # il est conseillé de le mettre au-dessus des autres apps
    # ...
    'django.contrib.admin',
]

AUTHENTICATION_BACKENDS = [
    # AxesBackend doit se trouver en premier.
    'axes.backends.AxesBackend',
    # Le backend d'authentification standard de Django
    'django.contrib.auth.backends.ModelBackend',
]

# 5 échecs entraînant un verrouillage pendant 10 minutes (valeur par défaut)
AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME = 0.166 # 0.166 * 60 = environ 10 minutes
  1. Migration : python manage.py migrate

Désormais, si quelqu'un échoue à se connecter de manière consécutive 5 fois, axes enregistrera cette tentative et bloquera la connexion pour l'IP/le compte pendant le temps déterminé.


4. Double verrouillage : Authentification à deux facteurs (2FA)

Le dernier rempart en cas de violation de mot de passe.

🤔 WHY : Pourquoi la 2FA est-elle nécessaire ?

Il se peut que le mot de passe du compte administrateur ait été compromis ou qu'il soit trop simple. L'authentification à deux facteurs exige à la fois "ce que je sais (le mot de passe)" et "ce que je possède (smartphone avec OTP)".

Même si un hacker vole le mot de passe, il ne pourra jamais se connecter sans le smartphone de l'administrateur.

HOW : Utiliser django-otp

django-otp est le paquet clé pour intégrer la 2FA dans Django.

  1. Installer : pip install django-otp

  2. Enregistrement dans settings.py :

INSTALLED_APPS = [
    # ...
    'django_otp',
    'django_otp.plugins.otp_totp', # support pour Google Authenticator, etc.
    # ...
]

MIDDLEWARE = [
    # ...
    'django_otp.middleware.OTPMiddleware', # après SessionMiddleware
    # ...
]

django-otp fournit la structure de base, et en utilisant ce type de paquet comme django-two-factor-auth simplifie grandement la mise en œuvre du processus d'enregistrement et de scannage de QR pour l'utilisateur.


5. Installer des pièges : Intégration de Honeypot et Fail2Ban

C'est la méthode de défense la plus proactive. Elle utilise les tentatives de scan de /admin de l'attaquant contre lui en le bannissant définitivement.

🤔 WHY : Pourquoi installer des pièges ?

De toute façon, les attaquants continueront à scanner /admin. Alors, pourquoi ne pas transformer ce chemin en piège (Honeypot) pour considérer toute IP essayant d'y accéder comme malveillante et la bloquer immédiatement ?

HOW : Faux Admin + Fail2Ban

Cette méthode est un peu complexe mais très efficace.

  1. Créer une vue d'Admin factice : Cachez la véritable URL d'administration comme décrit au point 1 en la remplaçant par my-secret-admin-portal-b7x9z/. Puis, connectez une vue factice à l'URL abandonnée /admin/.
# urls.py
from django.urls import path
from . import views # importer la vue factice

urlpatterns = [
    path('my-secret-admin-portal-b7x9z/', admin.site.urls), # vraie
    path('admin/', views.admin_honeypot), # factice (piège)
]

# views.py
import logging
from django.http import HttpResponseForbidden

# configuration du logger spécifique au honeypot (définir un logger 'honeypot' dans settings.py)
honeypot_logger = logging.getLogger('honeypot')

def admin_honeypot(request):
    # Enregistrez l'IP de la personne qui a tenté d'accéder
    ip = request.META.get('REMOTE_ADDR')
    honeypot_logger.warning(f"HONEYPOT: Tentative d'accès admin depuis {ip}")

    # Montrez simplement une erreur 403 à l'attaquant
    return HttpResponseForbidden()
  1. Configurer Fail2Ban : Fail2Ban surveille en temps réel les fichiers journaux du serveur, et lorsqu'il détecte des motifs spécifiques (ex : "HONEYPOT: ..."), il bloque l'IP à l'origine de ces logs dans iptables (le pare-feu Linux).

    • Configurez Django pour écrire le log dans honeypot.log.

    • Configurez Fail2Ban pour surveiller honeypot.log.

    • Lorsque quelqu'un accède à /admin/, views.py enregistrera cette tentative dans le log, et Fail2Ban la détectera, bloquant alors immédiatement tous les accès de cette IP (SSH, HTTP, etc.).

Résumé

La sécurité de la page d'administration Django repose sur la mise en place de multiples couches de défense.

  • (Obligatoire) 1. Changer l'URL : Investissez 5 minutes maintenant pour le faire.

  • (Recommandé) 2. Restriction IP : La plus puissante si vous avez une IP fixe.

  • (Recommandé) 3. django-axes : Pour empêcher les attaques par force brute.

  • (Fortement recommandé) 4. 2FA : Pour empêcher le vol du compte administrateur.

  • (Avancé) 5. Honeypot : Pour protéger le serveur de manière proactive.

Laisser /admin tel quel est une négligence en matière de sécurité. Vérifiez dès maintenant votre urls.py.