Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
django-defender — Une application Django réutilisable simple et super rapide qui bloque les tentatives de connexion par force brute. | Kitploit
Outils/GitHubGitHub/jazzband/django-defender
Outils DéfensifsAttaques de Mots de PasseSécurité WebAuthentification
GitHubjazzband/django-defender

django-defender

Une application Django réutilisable simple et super rapide qui bloque les tentatives de connexion par force brute.

Voir le dépôt
1.1k144il y a 6 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

django-defender

.. image:: https://jazzband.co/static/img/badge.svg :target: https://jazzband.co/ :alt: Jazzband

.. image:: https://img.shields.io/pypi/pyversions/django-defender.svg :alt: Versions Python prises en charge :target: https://pypi.org/project/django-defender/

.. image:: https://img.shields.io/pypi/djversions/django-defender.svg :target: https://pypi.org/project/django-defender/ :alt: Versions Django prises en charge

.. image:: https://github.com/jazzband/django-defender/workflows/Test/badge.svg :target: https://github.com/jazzband/django-defender/actions :alt: GitHub Actions

.. image:: https://codecov.io/gh/jazzband/django-defender/branch/master/graph/badge.svg :target: https://codecov.io/gh/jazzband/django-defender :alt: Couverture

.. image:: https://readthedocs.org/projects/django-defender/badge/?version=latest :alt: Statut de la documentation :target: https://django-defender.readthedocs.io/en/latest/?badge=latest

Une simple application Django réutilisable qui empêche les tentatives de connexion par force brute. L'objectif est de la rendre aussi rapide que possible, afin de ne pas ralentir les tentatives de connexion.

Nous utiliserons un cache pour éviter d'interroger la base de données à chaque tentative de connexion. La première version sera basée sur Redis, mais l'objectif est de rendre cela configurable afin que les utilisateurs puissent utiliser le backend qui correspond le mieux à leurs besoins.

Sites utilisant django-defender

Si vous utilisez defender sur votre site, soumettez une PR pour l'ajouter à la liste.

  • https://hub.docker.com
  • https://www.mycosbuilder.com

Documentation

La documentation est disponible sur Read the Docs :

https://django-defender.readthedocs.io

Fonctionnalités

  • Enregistrer toutes les tentatives de connexion dans la base de données

  • Support des proxys inverses avec différents en-têtes pour les adresses IP

  • Limitation de débit basée sur

    • Nom d'utilisateur
    • Adresse IP
  • Utilisation de Redis pour la liste noire

  • Configuration

    • Serveur Redis

      • Hôte
      • Port
      • Base de données
      • Mot de passe
      • Préfixe de clé
    • Durée du blocage

    • Nombre de tentatives incorrectes avant blocage

  • 95 % de couverture de code

  • Documentation complète

  • Capacité de stocker les tentatives de connexion dans la base de données

  • Commande de gestion pour nettoyer la table des tentatives de connexion

  • Pages d'administration

    • Liste des noms d'utilisateur et adresses IP bloqués
    • Liste des tentatives de connexion récentes
    • Possibilité de débloquer des personnes
  • Peut être facilement adapté à une méthode d'authentification personnalisée.

  • Des signaux sont envoyés lors du blocage d'un nom d'utilisateur ou d'une IP

Pages d'administration


.. image:: https://cloud.githubusercontent.com/assets/261601/5950540/8895b570-a729-11e4-9dc3-6b00e46c8043.png :target: https://cloud.githubusercontent.com/assets/261601/5950540/8895b570-a729-11e4-9dc3-6b00e46c8043.png :alt: alt tag

.. image:: https://cloud.githubusercontent.com/assets/261601/5950541/88a35194-a729-11e4-981b-3a55b44ef9d5.png :target: https://cloud.githubusercontent.com/assets/261601/5950541/88a35194-a729-11e4-981b-3a55b44ef9d5.png :alt: alt tag

Prérequis

  • Python : 3.8, 3.9, 3.10, 3.11, 3.12, PyPy
  • Django : 3.2, 4.2, 5.0, 5.1, 5.2
  • Redis : 5.x, 6.x, 7.x

Installation

Téléchargez le code et exécutez l'installation de l'une des manières suivantes selon la méthode.

Pour installer la version prête pour la production depuis PyPI :

.. code-block:: bash

pip install django-defender

Pour installer la version de développement à partir du code source après téléchargement :

.. code-block:: bash

python setup.py install

Pour installer la version de développement de la branche master depuis le dépôt GitHub :

.. code-block:: bash

pip install -e git+http://github.com/kencochran django-defender.git#egg=django_defender-dev

Tout d'abord, vous devez ajouter ce projet à votre liste INSTALLED_APPS dans settings.py

.. code-block:: python

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', # ... 'defender', # ... ]

Ensuite, installez le middleware FailedLoginMiddleware

.. code-block:: python

MIDDLEWARE_CLASSES = [ 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'defender.middleware.FailedLoginMiddleware', ]

Si vous souhaitez gérer les utilisateurs bloqués via l'administration Django, ajoutez ce qui suit à votre urls.py

.. code-block:: python

urlpatterns = [ path('admin/defender/', include('defender.urls')), # defender admin path('admin/', admin.site.urls), # normal admin # your own patterns follow... ]

Migrations


Vous devrez créer les tables nécessaires dans votre base de données.

.. code-block:: bash

python manage.py migrate defender

Commandes de gestion


cleanup_django_defender

Si votre site web a beaucoup de trafic, la table AccessAttempts se remplira rapidement. Si vous n'avez pas besoin de conserver les données à des fins d'audit, une commande de gestion vous aide à la nettoyer.

Elle utilise le paramètre DEFENDER_ACCESS_ATTEMPT_EXPIRATION pour déterminer les enregistrements à supprimer. Par défaut, si non spécifié, la durée est de 24 heures.

.. code-block:: bash

$ python manage.py cleanup_django_defender

Vous pouvez configurer cette commande comme une tâche cron quotidienne ou hebdomadaire pour réduire la taille de la table.

.. code-block:: bash

exécuté à 00:24 chaque matin.

24 0 * * * /usr/bin/python manage.py cleanup_django_defender >> /var/log/django_defender_cleanup.log

Objectifs à long terme

  • Backends enfichables, pour que les utilisateurs puissent utiliser autre chose que Redis
  • Envoyer un email aux utilisateurs lorsque leur compte est bloqué
  • Ajouter une liste blanche pour les noms d'utilisateur et ip qui ne seront jamais bloqués (administrateurs, etc.)
  • Ajouter une liste noire permanente pour les adresses IP
  • Analyser les IP de proxy connues et ne pas bloquer les requêtes provenant de celles-ci (améliorer les chances qu'une bonne IP ne soit pas bloquée)
  • Ajouter une commande de gestion pour supprimer les tentatives de connexion anciennes (configurables).

Performances


L'objectif de defender est d'être aussi rapide que possible afin de ne pas ralentir le processus de connexion. Pour vérifier que nos objectifs sont atteints, nous avons besoin d'un moyen de tester l'application. La meilleure façon est de comparer la rapidité d'une connexion Django normale avec defender et django-axes.

La connexion Django normale est notre base de référence, et nous nous attendons à ce qu'elle soit la plus rapide des trois méthodes, car il n'y a pas de vérifications supplémentaires.

La connexion avec defender sera probablement plus lente que la connexion Django, et nous espérons qu'elle sera plus rapide que la connexion avec django-axes. L'objectif est de minimiser la différence entre la connexion brute normale et defender.

La vitesse de connexion avec django-axes sera probablement la plus lente des trois car elle effectue plus de vérifications et beaucoup de requêtes en base de données.

La meilleure façon de déterminer la vitesse d'une connexion est d'effectuer un test de charge sur une application avec chaque configuration, et de comparer les temps de connexion pour chaque type.

Tests de charge


Pour couvrir tous les types de connexions, notre test de charge doit inclure plusieurs scénarios.

#. Tout succès : Nous effectuons un test de charge avec uniquement des connexions réussies.

#. Mixte : un mélange de succès et d'échecs : Nous testons avec à la fois des connexions réussies et échouées pour voir comment les échecs affectent les performances.

#. Tous échecs : Nous testons avec uniquement des échecs de connexion et comparons les performances.

Nous aurons besoin d'une application exemple pour le test de charge, la seule différence étant la configuration où nous chargeons defender, axes, ou aucun des deux.

Nous pouvons utiliser un service de test de charge hébergé, ou quelque chose comme jmeter. Quoi qu'il en soit, la méthode doit être cohérente pour tous les tests. Si nous utilisons jmeter, nous devrions fournir notre configuration jmeter pour que d'autres puissent exécuter les tests sur leur propre machine.

Résultats des tests de charge


Nous publierons les résultats ici. Nous expliquerons chaque test, et montrerons les résultats accompagnés de graphiques.

Pourquoi pas django-axes

django-axes est excellent mais il stocke tout dans la base de données, ce qui crée un goulot d'étranglement quand vous avez beaucoup de données. Cela ralentit les requêtes d'authentification de 200 à 300 ms. Cela peut être acceptable pour certains sites, mais trop long pour d'autres.

Ce projet a commencé comme un fork de django-axes, en réutilisant autant que possible leur code, en supprimant les parties inutiles et en accélérant les recherches pour améliorer la connexion.

Comment fonctionne django-defender

#. Lorsqu'une personne tente de se connecter, on vérifie d'abord si elle est actuellement bloquée. On vérifie le nom d'utilisateur qu'elle essaie d'utiliser, ainsi que l'adresse IP. Si elle est bloquée, passer à l'étape 5. Sinon, passer à l'étape 2.

#. La personne n'est pas bloquée, on vérifie donc si la connexion est valide. Si elle est valide, passer à l'étape 6. Sinon, passer à l'étape 3.

#. La tentative de connexion n'était pas valide. On ajoute son nom d'utilisateur et son adresse IP pour cette tentative dans le cache. Si cela dépasse la limite, on l'ajoute à la liste des bloqués, puis passer à l'étape 5. Si la limite n'est pas dépassée, passer à l'étape 4.

#. La connexion était invalide, mais sans dépasser la limite. La personne est renvoyée vers l'écran de connexion pour réessayer.

#. L'utilisateur est bloqué : Il est redirigé vers une page de blocage l'informant qu'il est bloqué et donnant une estimation du moment où il sera débloqué.

#. La connexion est valide. On réinitialise les tentatives de connexion échouées et on le redirige vers sa destination.

Backend de cache

Defender utilise le cache pour enregistrer les tentatives échouées.

Clés de cache


Compteurs :

  • prefix:failed:ip:[ip] (count, TTL)
  • prefix:failed:username:[username] (count, TTL)

Booléens (si présent, cela signifie bloqué) :

  • prefix:blocked:ip:[ip] (true, TTL)
  • prefix:blocked:username:[username] (true, TTL)

Personnalisation de django-defender

Vous avez plusieurs options pour personnaliser django-defender. Elles doivent être définies dans votre fichier settings.py.

  • DEFENDER_LOGIN_FAILURE_LIMIT\ : Int : Le nombre de tentatives de connexion autorisées avant qu'un enregistrement ne soit créé pour les échecs. [Par défaut : 3\ ]

  • DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME\ : Int : Le nombre de tentatives de connexion autorisées sur un nom d'utilisateur avant qu'un enregistrement ne soit créé pour les échecs. [Par défaut : DEFENDER_LOGIN_FAILURE_LIMIT\ ]

  • DEFENDER_LOGIN_FAILURE_LIMIT_IP\ : Int : Le nombre de tentatives de connexion autorisées depuis une IP avant qu'un enregistrement ne soit créé pour les échecs. [Par défaut : DEFENDER_LOGIN_FAILURE_LIMIT\ ]

  • DEFENDER_BEHIND_REVERSE_PROXY\ : Booléen : Y a-t-il un proxy inverse devant defender ? [Par défaut : False\ ]

  • DEFENDER_REVERSE_PROXY_HEADER\ : Chaîne : Le nom de l'en-tête HTTP contenant l'adresse IP du proxy inverse. [Par défaut : HTTP_X_FORWARDED_FOR\ ]

Justification de l'utilisation de DEFENDER_ATTEMPT_COOLOFF_TIME et DEFENDER_LOCKOUT_COOLOFF_TIME


Bien qu'utiliser DEFENDER_COOLOFF_TIME seul soit suffisant pour la plupart des cas d'usage, dans certains scénarios spécifiques comme un environnement de haute sécurité, les développeurs peuvent souhaiter un contrôle plus fin sur la durée pendant laquelle les tentatives de connexion invalides sont "mémorisées" lors de l'évaluation du verrouillage, par rapport à la durée pendant laquelle ces clés de verrouillage sont effectivement bloquées. DEFENDER_ATTEMPT_COOLOFF_TIME et DEFENDER_LOCKOUT_COOLOFF_TIME permettent cette configuration fine et précise.

Prenons également un exemple de faible sécurité et de faible échelle, comme le site web d'un lycée. Ce site pourrait être hébergé sur quelques ordinateurs de l'école et administré par le personnel informatique et les professeurs d'informatique (si la chance leur sourit). Dans ce scénario, on peut imaginer que de grandes parties du site sont accessibles sans authentification, mais que la connexion donne accès à des informations relativement privilégiées comme le nom, l'email, les notes et l'emploi du temps de l'élève. Enfin, comme un email est lié au compte, on suppose qu'il existe une fonctionnalité de réinitialisation de mot de passe qui débloque le compte une fois effectuée. Dans ce cas, on pourrait estimer qu'il n'est pas nécessaire de mémoriser les échecs de connexion pendant de longues périodes, car l'application souhaite simplement se protéger contre d'éventuelles attaques par déni de service. Cela pourrait être accompli en gardant DEFENDER_ATTEMPT_COOLOFF_TIME bas, disons 30 secondes, et en définissant DEFENDER_LOCKOUT_COOLOFF_TIME à une valeur beaucoup plus élevée, comme 600 secondes. En gardant DEFENDER_ATTEMPT_COOLOFF_TIME bas et en verrouillant les acteurs malveillants pendant des périodes significatives avec DEFENDER_LOCKOUT_COOLOFF_TIME élevé, les attaques rapides par force brute seront contrecarrées et le petit serveur disposera de plus d'espace dans son cache pour d'autres données. Et en offrant une fonctionnalité de réinitialisation de mot de passe comme décrit, ces administrateurs hypothétiques pourraient limiter leur implication dans le déblocage des vrais utilisateurs tout en préservant l'accessibilité souhaitée de leur site.

Bien que l'exemple précédent soit quelque peu artificiel, la pleine puissance de ces configurations est démontrée avec l'explication et l'exemple suivants.

Lorsque DEFENDER_STORE_ACCESS_ATTEMPTS est à True, DEFENDER_LOCKOUT_COOLOFF_TIME peut également être configuré comme une liste d'entiers. Configuré en liste, le nombre de tentatives de connexion échouées précédentes pour la clé de verrouillage configurée est divisé par DEFENDER_LOGIN_FAILURE_LIMIT pour produire un comptage intentionnellement surestimé du nombre d'échecs de connexion pour la période définie par DEFENDER_ACCESS_ATTEMPT_EXPIRATION. Cela finit par être une surestimation car le temps entre les tentatives échouées n'est pas pris en compte dans ce calcul. Bien que cela puisse sembler sévère, dans certains scénarios spécifiques, la protection supplémentaire contre les attaques plus lentes peut valoir\ l'éventuel\ désagrément\ causé aux vrais utilisateurs du système.

Un exemple pourrait être une application web accessible publiquement qui contient des informations sensibles sur ses utilisateurs (disons des relevés financiers personnels). L'application et les données qu'elle contient doivent être accessibles avec un minimum d'interruption, mais la sécurité est essentielle, donc des délais sont tolérés jusqu'à un certain point. Dans ces circonstances, on pourrait être tenté de simplement définir DEFENDER_COOLOFF_TIME sur un très grand entier voire 0 pour une protection maximale. Mais cela signifierait que si un\ vrai\ utilisateur\ est\ effectivement verrouillé, un administrateur devra le débloquer manuellement, ce qui est fastidieux et coûteux. En définissant DEFENDER_ATTEMPT_COOLOFF_TIME sur un nombre suffisamment grand, disons 600, et en définissant DEFENDER_LOCKOUT_COOLOFF_TIME comme une liste d'entiers croissants (par ex. [60, 120, 300, 600, 0]), nous pouvons protéger notre application théorique de manière comparable à si nous avions simplement défini DEFENDER_COOLOFF_TIME sur 600 tout en perturbant nos utilisateurs beaucoup moins.

Adaptation à d'autres méthodes d'authentification

defender peut être utilisé pour l'authentification autre que le système d'authentification Django. Par exemple, si l'authentification django-rest-framework doit être protégée contre les attaques par force brute, une méthode d'authentification personnalisée peut être implémentée.

Voici un exemple de classe BasicAuthenticationDefender basée sur djangorestframework.BasicAuthentication\ :

.. code-block:: python

import base64 import binascii

from django.utils.translation import gettext_lazy as _

from rest_framework import HTTP_HEADER_ENCODING, exceptions from rest_framework.authentication import ( BasicAuthentication, get_authorization_header, )

from defender import utils from defender import config

class BasicAuthenticationDefender(BasicAuthentication):def get_username_from_request(self, request): auth = get_authorization_header(request).split() return base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')[0]

def authenticate(self, request): auth = get_authorization_header(request).split()

root@kitploit:~
if not auth or auth[0].lower() != b'basic':
    return None

if len(auth) == 1:
    msg = _('Invalid basic header. No credentials provided.')
    raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
    msg = _('Invalid basic header. Credentials string should not contain spaces.')
    raise exceptions.AuthenticationFailed(msg)

if utils.is_already_locked(request, get_username=self.get_username_from_request):
    detail = "You have attempted to login {failure_limit} times, with no success." \
             "Your account is locked for {cooloff_time_seconds} seconds" \
             "".format(
                failure_limit=config.FAILURE_LIMIT,
                cooloff_time_seconds=config.LOCKOUT_COOLOFF_TIME[
                   defender_utils.get_lockout_cooloff_time(username=self.get_username_from_request(request))
                ]
             )
    raise exceptions.AuthenticationFailed(_(detail))

try:
    auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError, binascii.Error):
    msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
    raise exceptions.AuthenticationFailed(msg)

userid, password = auth_parts[0], auth_parts[2]
login_unsuccessful = False
login_exception = None
try:
    response = self.authenticate_credentials(userid, password)
except exceptions.AuthenticationFailed as e:
    login_unsuccessful = True
    login_exception = e

utils.add_login_attempt_to_db(request,
                              login_valid=not login_unsuccessful,
                              get_username=self.get_username_from_request)
# add the failed attempt to Redis in case of a failed login or resets the attempt count in case of success
utils.check_request(request,
                    login_unsuccessful=login_unsuccessful,
                    get_username=self.get_username_from_request)
if login_unsuccessful:
    raise login_exception

return response

Pour le faire fonctionner, ajoutez BasicAuthenticationDefender à DEFAULT_AUTHENTICATION_CLASSES avant toutes les autres méthodes d'authentification dans votre settings.py.

Adaptation à d'autres méthodes d'authentification :- django-rest-auth dans djangorestframework

defender peut être intégré avec la combinaison de django-rest-framework et django-rest-auth qui permet d'authentifier les utilisateurs.

Références


  • https://www.django-rest-framework.org/
  • https://django-rest-auth.readthedocs.io/en/latest/

Voici un exemple de classe BasicAuthenticationDefender basée sur rest_framework.authentication.TokenAuthentication qui utilise la bibliothèque django-rest-auth pour l'authentification des utilisateurs.

.. code-block:: python

import base64 import binascii

from django.conf import settings from django.contrib.auth import get_user_model, authenticate from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm from django.contrib.auth.tokens import default_token_generator from django.utils.http import urlsafe_base64_decode as uid_decoder from django.utils.translation import gettext_lazy as _ from django.utils.encoding import force_str from rest_framework import serializers, exceptions, HTTP_HEADER_ENCODING from rest_framework.exceptions import ValidationError from defender import utils as defender_utils from defender import config from rest_framework.authentication import ( get_authorization_header, )

Get the UserModel

UserModel = get_user_model()

class BasicAuthenticationDefender(serializers.Serializer):

root@kitploit:~
  username = serializers.CharField(required=False, allow_blank=True)
  email = serializers.EmailField(required=False, allow_blank=True)
  password = serializers.CharField(style={'input_type': 'password'})

  def authenticate(self, **kwargs):
    request = self.context['request']

    if hasattr(settings, 'ACCOUNT_AUTHENTICATION_METHOD'):
        login_field = settings.ACCOUNT_AUTHENTICATION_METHOD
    else:
        login_field = 'username'
    userid = self.username_from_request(request, login_field)

    if defender_utils.is_already_locked(request, username=userid):
        detail = "You have attempted to login {failure_limit} times with no success. "
                 .format(
                     failure_limit=config.FAILURE_LIMIT,
                     cooloff_time_seconds=config.LOCKOUT_COOLOFF_TIME[defender_utils.get_lockout_cooloff_time(username=userid)]
                 )
        raise exceptions.AuthenticationFailed(_(detail))

    login_unsuccessful = False
    login_exception = None
    try:
        response = authenticate(request, **kwargs)
        if response == None:
            login_unsuccessful = True
            msg = _('Unable to log in with provided credentials.')
            # raise exceptions.ValidationError(msg)
            login_exception = exceptions.ValidationError(msg)
    except exceptions.AuthenticationFailed as e:
        login_unsuccessful = True
        login_exception = e

    defender_utils.add_login_attempt_to_db(request,
                                           login_valid=not login_unsuccessful,
                                           username=userid)

    user_not_blocked = defender_utils.check_request(request,
                                                    login_unsuccessful=login_unsuccessful,
                                                    username=userid)
    if user_not_blocked and not login_unsuccessful:
        return response

    raise login_exception

  def _validate_email(self, email, password):
    user = None

    if email and password:
        user = self.authenticate(email=email, password=password)
    else:
        msg = _('Must include "email" and "password".')
        raise exceptions.ValidationError(msg)

    return user

  def _validate_username(self, username, password):
    user = None

    if username and password:
        user = self.authenticate(username=username, password=password)
    else:
        msg = _('Must include "username" and "password".')
        raise exceptions.ValidationError(msg)

    return user

  def _validate_username_email(self, username, email, password):
    user = None

    if email and password:
        user = self.authenticate(email=email, password=password)
    elif username and password:
        user = self.authenticate(username=username, password=password)
    else:
        msg = _('Must include either "username" or "email" and "password".')
        raise exceptions.ValidationError(msg)

    return user

  def validate(self, attrs):
    username = attrs.get('username')
    email = attrs.get('email')
    password = attrs.get('password')

    user = None

    if 'allauth' in settings.INSTALLED_APPS:
        from allauth.account import app_settings

        # Authentication through email
        if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
            user = self._validate_email(email, password)

        # Authentication through username
        elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
            user = self._validate_username(username, password)

        # Authentication through either username or email
        else:
            user = self._validate_username_email(username, email, password)

    else:
        # Authentication without using allauth
        if email:
            try:
                username = UserModel.objects.get(
                    email__iexact=email).username()
            except UserModel.DoesNotExist:
                pass

        if username:
            user = self._validate_username_email(username, '', password)

    # Did we get back an active user?
    if user:
        if not user.is_active:
            msg = _('User account is disabled.')
            raise exceptions.ValidationError(msg)
    else:
        msg = _('Unable to log in with provided credentials.')
        raise exceptions.ValidationError(msg)

    # If required, is the email verified?
    if 'rest_auth.registration' in settings.INSTALLED_APPS:
        from allauth.account import app_settings
        if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
            email_address = user.emailaddress_set.get(email=user.email)
            if not email_address.verified:
                raise serializers.ValidationError(
                    _('E-mail is not verified.'))

    attrs['user'] = user
    return attrs

  def username_from_request(self, request, login_field):
    user_data = request._data
    return user_data[login_field]

Pour le faire fonctionner, ajoutez BasicAuthenticationDefender au dictionnaire REST_AUTH_SERIALIZERS dans votre settings.py sous la clé LOGIN_SERIALIZER. Par exemple, dans votre settings.py ajoutez la ligne suivante :

.. code-block:: python

REST_AUTH_SERIALIZERS = { 'LOGIN_SERIALIZER': '.BasicAuthenticationDefender', }

Adaptation pour les formulaires de réinitialisation de mot de passe

defender peut être adapté pour la PasswordResetView de Django afin d'éviter trop de soumissions.

Nous devons créer de nouvelles vues qui sous-classent les vues intégrées de Django LoginView, PasswordResetView et PasswordResetConfirmView — puis utiliser ces vues dans notre urls.py en remplacement de celles intégrées.

Les vues bloquent en fonction de l'adresse e-mail soumise dans le formulaire de réinitialisation du mot de passe. Ceci est différent de l'implémentation par défaut (qui utilise le nom d'utilisateur), donc nous devons faire attention à nettoyer après nous lors de la connexion et de la réinitialisation du mot de passe terminée.

.. code-block:: python

root@kitploit:~
from defender import utils as def_utils
from django.contrib.auth import views as auth_views

class UserSignIn(auth_views.LoginView):
    def form_valid(self, form):
        """Force clear all the cached Defender statues for the authenticated user’s email address."""
        super_valid = super().form_valid(form)
        def_utils.check_request(self.request, False, username=form.get_user().email)
        return super_valid

class PasswordResetBruteForceProtectedView(auth_views.PasswordResetView):
    def get(self, request, *args, **kwargs):
        """Confirm the user isn’t already blocked by IP before showing the password reset view."""
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """
        Confirm the user isn’t already blocked by IP before allowing form POST.

        Also, force log this form POST as a single entry in the Defender cache, against the submitted email address.
        """
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        def_utils.check_request(
            request, login_unsuccessful=True, username=request.POST.get("email")
        )
        return super().post(request, *args, **kwargs)


class PasswordResetConfirmBruceForceProtectedView(auth_views.PasswordResetConfirmView):
    def get(self, request, *args, **kwargs):
        """Confirm the user isn’t already blocked by IP before showing the password confirm view."""
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        """Confirm the user isn’t already blocked by IP before allowing form POST for the password change confirmation."""
        if def_utils.is_already_locked(request):
            return def_utils.lockout_response(request)
        return super().post(request, *args, **kwargs)

    def form_valid(self, form):
        """Force clear all the cached Defender statues for the user’s email address after successfully changing their password."""
        super_valid = super().form_valid(form)
        def_utils.check_request(
            self.request, login_unsuccessful=False, username=self.user.email
        )
        return super_valid

Signaux Django

django-defender envoie des signaux lors du blocage d'un nom d'utilisateur ou d'une adresse IP. Pour configurer les fonctions de récepteur de signaux :

.. code-block:: python

from django.dispatch import receiver

from defender import signals

@receiver(signals.username_block) def username_blocked(username, **kwargs): print("%s was blocked!" % username)

@receiver(signals.ip_block) def ip_blocked(ip_address, **kwargs): print("%s was blocked!" % ip_address)

Exécution des tests

Les tests peuvent être exécutés, après avoir cloné le dépôt et avoir Django installé, comme suit :

.. code-block:: bash

PYTHONPATH=$PYTHONPATH:$PWD django-admin test defender --settings=defender.test_settings

Avec la couverture de code :

.. code-block:: bash

PYTHONPATH=$PYTHONPATH:$PWD coverage run --source=defender $(which django-admin) test defender --settings=defender.test_settings

Publication

#. python setup.py sdist #. twine upload dist/*

Télécharger l’outil

DEFENDER_LOCK_OUT_BY_IP_AND_USERNAME\ : Booléen : Verrouille un utilisateur en fonction de la combinaison IP et nom d'utilisateur. Cela empêche un utilisateur de bloquer l'accès à l'application pour tous les autres utilisateurs derrière la même adresse IP. [Par défaut : False\ ]

  • DEFENDER_DISABLE_IP_LOCKOUT\ : Booléen : Si True, ne verrouille pas l'adresse IP de l'utilisateur, seul le nom d'utilisateur sera verrouillé. [Par défaut : False]

  • DEFENDER_DISABLE_USERNAME_LOCKOUT\ : Booléen : Si True, ne verrouille pas les noms d'utilisateur, seules les adresses IP seront verrouillées. [Par défaut : False]

  • DEFENDER_COOLOFF_TIME\ : Int : Si défini, définit une période d'inactivité après laquelle les anciennes tentatives échouées et les verrouillages de nom d'utilisateur/IP seront oubliés. Un entier, interprété en secondes. Si 0, ni les tentatives échouées ni les verrouillages n'expirent. [Par défaut : 300\ ]

  • DEFENDER_ATTEMPT_COOLOFF_TIME\ : Int : Si défini, remplace la période d'inactivité après laquelle les anciennes tentatives échouées seront oubliées, définie par DEFENDER_COOLOFF_TIME. Un entier, interprété en secondes. Si 0, les tentatives échouées n'expirent pas. [Par défaut : DEFENDER_COOLOFF_TIME\ ]

  • DEFENDER_LOCKOUT_COOLOFF_TIME\ : Int ou Liste : Si défini, remplace la période d'inactivité après laquelle les verrouillages de nom d'utilisateur/IP seront oubliés, définie par DEFENDER_COOLOFF_TIME. Un entier, interprété en secondes. Une liste d'entiers, interprétés comme un nombre de secondes pour les utilisateurs, l'indice de l'entier correspondant au nombre de verrouillages précédents (jusqu'à un maximum) survenus au cours des DEFENDER_ACCESS_ATTEMPT_EXPIRATION dernières heures. Si la propriété est définie sur 0 ou [], le verrouillage n'expire pas. [Par défaut : DEFENDER_COOLOFF_TIME\ ]

  • DEFENDER_LOCKOUT_TEMPLATE\ : Chaîne : [Par défaut : None\ ] Si défini, spécifie un template à afficher lorsqu'un utilisateur est verrouillé. Le template reçoit les variables de contexte suivantes :

    • cooloff_time_seconds\ : Le temps de refroidissement en secondes
    • cooloff_time_minutes\ : Le temps de refroidissement en minutes
    • failure_limit\ : Le nombre d'échecs avant blocage.
  • DEFENDER_USERNAME_FORM_FIELD\ : Chaîne : Le nom du champ de formulaire contenant les noms d'utilisateur. [Par défaut : username\ ]

  • DEFENDER_CACHE_PREFIX\ : Chaîne : Le préfixe de cache pour les clés defender. [Par défaut : defender\ ]

  • DEFENDER_LOCKOUT_URL\ : Chaîne : L'URL vers laquelle rediriger si une personne est verrouillée.

  • DEFENDER_REDIS_URL\ : Chaîne : L'URL Redis pour defender. [Par défaut : redis://localhost:6379/0\ ] (Exemple avec mot de passe : redis://:mypassword@localhost:6379/0\ )

  • DEFENDER_REDIS_PASSWORD_QUOTE\ : Booléen : Si des caractères spéciaux sont dans le mot de passe Redis (comme '@'), on peut citer le mot de passe avec urllib.parse.quote("password!@#"), et mettre à True. [Par défaut : False\ ]

  • DEFENDER_REDIS_NAME\ : Chaîne : Le nom du cache depuis CACHES dans vos paramètres Django (par ex. "default"). Si défini, DEFENDER_REDIS_URL sera ignoré. [Par défaut : None\ ]

  • DEFENDER_STORE_ACCESS_ATTEMPTS\ : Booléen : Si vous souhaitez stocker la tentative de connexion dans la base de données, mettez à True. Si False, elle n'est pas sauvegardée. [Par défaut : True\ ]

  • DEFENDER_USE_CELERY\ : Booléen : Si vous souhaitez utiliser Celery pour stocker la tentative de connexion dans la base de données, mettez à True. Si False, elle est sauvegardée de manière synchrone. [Par défaut : False\ ]

  • DEFENDER_ACCESS_ATTEMPT_EXPIRATION\ : Int : Durée en heures pendant laquelle les enregistrements de tentatives de connexion sont conservés avant que la commande de gestion ne les nettoie. [Par défaut : 24\ ]

  • DEFENDER_GET_USERNAME_FROM_REQUEST_PATH\ : Chaîne : Le chemin d'importation de la fonction qui accède au nom d'utilisateur depuis la requête. Si vous souhaitez utiliser une fonction personnalisée pour accéder et traiter le nom d'utilisateur depuis la requête, vous pouvez la spécifier ici. [Par défaut : defender.utils.username_from_request\ ]