Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
django-defender — Eine einfache, superschnelle, wiederverwendbare Django-App, die Brute-Force-Anmeldeversuche blockiert | Kitploit
Tools/GitHubGitHub/jazzband/django-defender
DefensivwerkzeugePasswortangriffeWebsicherheitAuthentifizierung
GitHubjazzband/django-defender

django-defender

Eine einfache, superschnelle, wiederverwendbare Django-App, die Brute-Force-Anmeldeversuche blockiert

Repository anzeigen
1.1k144vor 6 MonatenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

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: Unterstützte Python-Versionen :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: Unterstützte Django-Versionen

.. 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: Coverage

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

Eine einfache, wiederverwendbare Django-App, die Brute-Force-Loginversuche blockiert. Ziel ist es, dies so schnell wie möglich zu machen, damit die Loginversuche nicht verlangsamt werden.

Wir verwenden einen Cache, damit für jeden Loginversuch nicht die Datenbank abgefragt werden muss. Die erste Version basiert auf Redis, aber das Ziel ist es, dies konfigurierbar zu machen, sodass Benutzer das Backend verwenden können, das ihren Anforderungen am besten entspricht.

Websites, die django-defender verwenden

Wenn Sie defender auf Ihrer Website verwenden, reichen Sie einen PR ein, um zur Liste hinzugefügt zu werden.

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

Dokumentation

Die Dokumentation ist auf Read the Docs verfügbar:

https://django-defender.readthedocs.io

Funktionen

  • Alle Anmeldeversuche in der Datenbank protokollieren

  • Unterstützung für Reverse Proxies mit verschiedenen Headern für IP-Adressen

  • Ratenbegrenzung basierend auf

    • Benutzername
    • IP-Adresse
  • Redis für die Blacklist verwenden

  • Konfiguration

    • Redis-Server

      • Host
      • Port
      • Datenbank
      • Passwort
      • Schlüsselpräfix
    • Sperrdauer

    • Anzahl fehlerhafter Versuche vor Sperrung

  • 95% Codeabdeckung

  • Vollständige Dokumentation

  • Möglichkeit, Anmeldeversuche in der Datenbank zu speichern

  • Management-Befehl zum Bereinigen der Datenbanktabelle für Anmeldeversuche

  • Admin-Seiten

    • Liste gesperrter Benutzernamen und IP-Adressen
    • Liste der letzten Anmeldeversuche
    • Möglichkeit, Personen zu entsperren
  • Kann leicht an eine benutzerdefinierte Authentifizierungsmethode angepasst werden.

  • Signale werden beim Sperren von Benutzername oder IP gesendet

Admin-Seiten


.. 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

Anforderungen

  • 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

Laden Sie den Code herunter und führen Sie die Installation auf eine der folgenden Arten aus, je nach Methode.

Um die produktionsreife Version von PyPI zu installieren:

.. code-block:: bash

pip install django-defender

Um die Entwicklungsversion aus dem Quellcode nach dem Herunterladen zu installieren:

.. code-block:: bash

python setup.py install

Um die Entwicklungsversion des Master-Branches aus dem GitHub-Repository zu installieren:

.. code-block:: bash

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

Zuerst müssen Sie dieses Projekt zu Ihrer Liste der INSTALLED_APPS in settings.py hinzufügen:

.. code-block:: python

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

Als nächstes installieren Sie die Middleware FailedLoginMiddleware:

.. code-block:: python

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

Wenn Sie die gesperrten Benutzer über das Django-Admin verwalten möchten, fügen Sie Folgendes zu Ihrer urls.py hinzu:

.. code-block:: python

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

Migrationen


Sie müssen in Ihrer Datenbank die für den Betrieb notwendigen Tabellen erstellen:

.. code-block:: bash

python manage.py migrate defender

Management-Befehle


cleanup_django_defender

Wenn Sie eine Website mit viel Traffic haben, wird die Tabelle AccessAttempts ziemlich schnell voll. Wenn Sie die Daten nicht zu Prüfzwecken aufbewahren müssen, gibt es einen Management-Befehl, der Ihnen hilft, sie sauber zu halten.

Dieser Befehl prüft Ihre Einstellung DEFENDER_ACCESS_ATTEMPT_EXPIRATION, um zu bestimmen, welche Datensätze gelöscht werden. Der Standardwert, falls nicht angegeben, ist 24 Stunden.

.. code-block:: bash

$ python manage.py cleanup_django_defender

Sie können dies als täglichen oder wöchentlichen Cron-Job einrichten, um die Tabellengröße gering zu halten:

.. code-block:: bash

run at 12:24 AM every morning.

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

Langfristige Ziele

  • Plug-in-fähige Backends, damit andere als Redis verwendet werden können
  • Benutzer per E-Mail benachrichtigen, wenn ihr Konto gesperrt wird
  • Eine Whitelist für Benutzernamen und IPs hinzufügen, die wir nie sperren (Admins usw.)
  • Eine permanente Blacklist für IP-Adressen hinzufügen
  • Nach bekannten Proxy-IPs scannen und Anfragen von diesen nicht blockieren (die Wahrscheinlichkeit erhöhen, dass eine gute IP blockiert wird)
  • Management-Befehl zum Löschen alter (konfigurierbarer) Anmeldeversuche hinzufügen

Leistung


Das Ziel von defender ist es, es so schnell wie möglich zu machen, damit es den Anmeldeprozess nicht verlangsamt. Um sicherzustellen, dass unsere Ziele erreicht werden, benötigen wir eine Möglichkeit, die Anwendung zu testen, um sicherzustellen, dass wir auf dem richtigen Weg sind. Der beste Weg, dies zu tun, ist zu vergleichen, wie schnell ein normaler Django-Login mit defender und django-axes ist.

Der normale Django-Login wäre unsere Basislinie, und wir erwarten, dass er die schnellste der drei Methoden ist, da keine zusätzlichen Überprüfungen stattfinden.

Der defender-Login wäre wahrscheinlich langsamer als der Django-Login und hoffentlich schneller als der django-axes-Login. Ziel ist es, den Unterschied zwischen dem normalen rohen Login und defender so gering wie möglich zu halten.

Die Geschwindigkeit des django-axes-Logins wird wahrscheinlich die langsamste der drei sein, da es mehr Überprüfungen durchführt und viele Datenbankabfragen macht.

Die beste Methode, um die Geschwindigkeit eines Logins zu bestimmen, ist ein Auslastungstest gegen eine Anwendung mit jedem Setup und der Vergleich der Login-Zeiten für jeden Typ.

Auslastungstests


Um sicherzustellen, dass wir alle verschiedenen Arten von Logins abdecken, müssen wir in unserem Auslastungstest mehr als einen Test haben.

#. Nur Erfolge: Wir führen einen Auslastungstest mit ausschließlich erfolgreichen Logins durch.

#. Gemischt: einige erfolgreiche, einige fehlgeschlagene: Wir testen mit einigen erfolgreichen und einigen fehlgeschlagenen Logins, um zu sehen, wie sich die Fehler auf die Leistung auswirken.

#. Nur Fehler: Wir testen mit allen fehlgeschlagenen Logins und sehen den Unterschied in der Leistung.

Wir benötigen eine Beispielanwendung, die wir für den Auslastungstest verwenden können, wobei der einzige Unterschied die Konfiguration ist, bei der wir entweder defender, axes oder keine von beiden laden.

Wir können einen gehosteten Auslastungstestdienst oder etwas wie JMeter verwenden. In jedem Fall müssen wir bei allen Tests konsistent sein. Wenn wir JMeter verwenden, sollten wir unsere JMeter-Konfiguration bereitstellen, damit andere die Tests selbst durchführen können.

Ergebnisse der Auslastungstests


Wir werden die Ergebnisse hier veröffentlichen. Wir werden jeden Test erklären und die Ergebnisse zusammen mit einigen Diagrammen zeigen.

Warum nicht django-axes

django-axes ist großartig, aber es legt alles in der Datenbank ab, und das verursacht einen Engpass, wenn man viele Daten hat. Es verlangsamt die Authentifizierungsanfragen um bis zu 200-300ms. Das mag für einige Websites nicht viel sein, aber für andere ist es zu lang.

Dies begann als Fork von django-axes, verwendet so viel von deren Code wie möglich, entfernt die nicht benötigten Teile und beschleunigt die Lookups, um den Login zu verbessern.

Wie django-defender funktioniert

#. Wenn jemand versucht, sich anzumelden, überprüfen wir zunächst, ob er derzeit gesperrt ist. Wir überprüfen den Benutzernamen, den er zu verwenden versucht, sowie die IP-Adresse. Wenn er gesperrt ist, gehe zu Schritt 5. Wenn nicht gesperrt, gehe zu Schritt 2.

#. Er ist nicht gesperrt, also überprüfen wir, ob der Login gültig war. Wenn gültig, gehe zu Schritt 6. Wenn nicht gültig, gehe zu Schritt 3.

#. Der Anmeldeversuch war nicht gültig. Fügen Sie seinen Benutzernamen und seine IP-Adresse für diesen Versuch zum Cache hinzu. Wenn dies ihn über das Limit bringt, fügen Sie ihn zur gesperrten Liste hinzu und gehen Sie dann zu Schritt 5. Wenn nicht über dem Limit, gehe zu Schritt 4.

#. Der Login war ungültig, aber nicht über dem Limit. Senden Sie ihn zurück zum Anmeldebildschirm, um es erneut zu versuchen.

#. Benutzer ist gesperrt: Senden Sie ihn zur gesperrten Seite, teilen Sie ihm mit, dass er gesperrt ist, und geben Sie eine Schätzung, wann er entsperrt wird.

#. Login ist gültig. Setzen Sie alle fehlgeschlagenen Anmeldeversuche zurück und leiten Sie ihn an sein Ziel weiter.

Cache-Backend

Defender verwendet den Cache, um die fehlgeschlagenen Versuche zu speichern.

Cache-Schlüssel


Zähler:

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

Boolesche Werte (wenn vorhanden, ist gesperrt):

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

Anpassen von django-defender

Sie haben einige Möglichkeiten, django-defender anzupassen. Diese sollten in Ihrer settings.py-Datei definiert werden.

  • DEFENDER_LOGIN_FAILURE_LIMIT\ : Int: Die Anzahl der erlaubten Anmeldeversuche, bevor ein Datensatz für die fehlgeschlagenen Logins erstellt wird. [Standard: 3\ ]

  • DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME\ : Int: Die Anzahl der erlaubten Anmeldeversuche für einen Benutzernamen, bevor ein Datensatz für die fehlgeschlagenen Logins erstellt wird. [Standard: DEFENDER_LOGIN_FAILURE_LIMIT\ ]

  • DEFENDER_LOGIN_FAILURE_LIMIT_IP\ : Int: Die Anzahl der erlaubten Anmeldeversuche von einer IP, bevor ein Datensatz für die fehlgeschlagenen Logins erstellt wird. [Standard: DEFENDER_LOGIN_FAILURE_LIMIT\ ]

  • DEFENDER_BEHIND_REVERSE_PROXY\ : Boolean: Befindet sich defender hinter einem Reverse Proxy? [Standard: False\ ]

  • DEFENDER_REVERSE_PROXY_HEADER\ : String: Der Name des HTTP-Headers mit Ihrer Reverse-Proxy-IP-Adresse. [Standard: HTTP_X_FORWARDED_FOR\ ]

  • \ : Boolean: Sperrt einen Benutzer basierend auf einer Kombination von IP und Benutzername aus. Dies verhindert, dass ein Benutzer anderen Benutzern, die von derselben IP-Adresse aus auf die App zugreifen, den Zugriff verweigert. [Standard: \ ]

Begründung für die Verwendung von DEFENDER_ATTEMPT_COOLOFF_TIME und DEFENDER_LOCKOUT_COOLOFF_TIME


Während die alleinige Verwendung von DEFENDER_COOLOFF_TIME für die meisten Anwendungsfälle ausreichend ist, möchten Entwickler in einigen spezifischen Szenarien, wie z. B. in einer hochsicherheitsrelevanten Umgebung, möglicherweise eine feinere Kontrolle darüber haben, wie lange ungültige Anmeldeversuche "gemerkt" werden, während sie für eine Sperrung in Betracht gezogen werden, im Vergleich zu der Zeit, die diese Sperrschlüssel tatsächlich vom System gesperrt sind. DEFENDER_ATTEMPT_COOLOFF_TIME und DEFENDER_LOCKOUT_COOLOFF_TIME ermöglichen genau diese fein granulare Konfiguration.

Wir können auch ein Beispiel mit niedriger Sicherheit und niedrigem Maßstab nehmen, wie die Website einer Highschool. Eine solche Website könnte auf einigen Computern der Schule laufen und von den IT-Mitarbeitern der Schule und den Informatiklehrern (falls sie überhaupt welche haben) verwaltet werden. In diesem Szenario können wir uns vorstellen, dass es bedeutende Teile der Website gibt, die ohne Authentifizierung zugänglich sind, aber die Anmeldung auf der Website könnte Zugang zu einigen relativ privilegierten Informationen wie dem Namen, der E-Mail, den Noten und dem Stundenplan des Schülers bieten. Schließlich werden wir annehmen, dass es eine Passwort-Zurücksetzen-Funktionalität gibt, die das Konto nach Abschluss entsperrt, da eine E-Mail mit dem Konto verknüpft ist. In einem solchen Fall könnte man sich vorstellen, dass es nicht notwendig ist, sich über lange Zeiträume an fehlgeschlagene Logins zu erinnern, da die Anwendung lediglich vor potenziellen Denial-of-Service-Angriffen schützen möchte. Dies könnte erreicht werden, indem man DEFENDER_ATTEMPT_COOLOFF_TIME niedrig hält, sagen wir 30 Sekunden, und DEFENDER_LOCKOUT_COOLOFF_TIME auf etwas viel Höheres wie 600 Sekunden setzt. Indem man DEFENDER_ATTEMPT_COOLOFF_TIME niedrig hält und böswillige Akteure durch die Einstellung von DEFENDER_LOCKOUT_COOLOFF_TIME auf einen hohen Wert für erhebliche Zeiträume sperrt, werden schnelle Brute-Force-Loginangriffe dennoch abgewehrt und ihr kleiner Server hat mehr Platz in seinem Cache für andere Daten. Und durch die Bereitstellung der Passwort-Zurücksetzen-Funktionalität wie oben beschrieben, könnten diese hypothetischen Administratoren ihre erforderliche Beteiligung am Entsperren echter Benutzer begrenzen und gleichzeitig die beabsichtigte Zugänglichkeit ihrer Website beibehalten.

Während das vorherige Beispiel etwas konstruiert ist, wird die volle Leistungsfähigkeit dieser Konfigurationen mit der folgenden Erklärung und dem Beispiel demonstriert.

Wenn DEFENDER_STORE_ACCESS_ATTEMPTS auf True gesetzt ist, kann DEFENDER_LOCKOUT_COOLOFF_TIME auch als Liste von ganzen Zahlen konfiguriert werden. Wenn es als Liste konfiguriert ist, wird die Anzahl der vorherigen fehlgeschlagenen Anmeldeversuche für den konfigurierten Sperrschlüssel durch DEFENDER_LOGIN_FAILURE_LIMIT geteilt, um eine absichtlich überschätzte Anzahl der fehlgeschlagenen Logins für den durch DEFENDER_ACCESS_ATTEMPT_EXPIRATION definierten Zeitraum zu erzeugen. Dies stellt sich als Überschätzung heraus, da die Zeit zwischen den fehlgeschlagenen Anmeldeversuchen bei dieser Berechnung nicht berücksichtigt wird. Obwohl dies hart erscheinen mag, kann der zusätzliche Schutz gegen langsamere Angriffe in bestimmten Szenarien die potenziellen Unannehmlichkeiten für echte Benutzer des Systems wert sein.

Ein solches Beispiel könnte eine öffentlich zugängliche Webanwendung sein, die sensible Informationen ihrer Benutzer beherbergt (sagen wir persönliche Finanzunterlagen). Die Anwendung und die darin enthaltenen Daten sollten mit minimalen Unterbrechungen zugänglich sein, aber die Sicherheit ist integraler Bestandteil, sodass Verzögerungen bis zu einem gewissen Punkt toleriert werden können. Unter diesen Umständen könnten wir den Wunsch haben, einfach DEFENDER_COOLOFF_TIME auf eine sehr große ganze Zahl oder sogar 0 für maximalen Schutz zu setzen. Dies würde jedoch bedeuten, dass ein echter Benutzer, der aus dem System ausgesperrt wird, von einem Administrator manuell entsperrt werden müsste, was natürlich umständlich und teuer ist. Indem wir DEFENDER_ATTEMPT_COOLOFF_TIME auf eine ausreichend große Zahl setzen, sagen wir 600, und DEFENDER_LOCKOUT_COOLOFF_TIME auf eine Liste steigender ganzer Zahlen (z. B. [60, 120, 300, 600, 0]) setzen, können wir unsere theoretische Anwendung vergleichbar schützen, als ob wir einfach DEFENDER_COOLOFF_TIME auf 600 gesetzt hätten, während wir unsere Benutzer deutlich weniger beeinträchtigen.

Anpassung an andere Authentifizierungsmethoden

defender kann für andere Authentifizierungen als das Django-Authentifizierungssystem verwendet werden. Z. B. falls die Authentifizierung von django-rest-framework vor Brute-Force-Angriffen geschützt werden muss, kann eine benutzerdefinierte Authentifizierungsmethode implementiert werden.

Es gibt eine Beispielklasse BasicAuthenticationDefender basierend auf 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]

root@kitploit:~
   def authenticate(self, request):
       auth = get_authorization_header(request).split()

       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

Damit es funktioniert, fügen Sie BasicAuthenticationDefender zu DEFAULT_AUTHENTICATION_CLASSES oberhalb aller anderen Authentifizierungsmethoden in Ihrer settings.py hinzu.

Anpassung an andere Authentifizierungsmethoden :- django-rest-auth in djangorestframework

defender kann in Kombination mit django-rest-framework und django-rest-auth integriert werden, die zur Benutzerauthentifizierung verwendet werden können.

Referenz


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

Nachfolgend finden Sie eine Beispielklasse BasicAuthenticationDefender basierend auf rest_framework.authentication.TokenAuthentication, die die Bibliothek django-rest-auth zur Benutzerauthentifizierung verwendet.

.. 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]

Damit es funktioniert, fügen Sie BasicAuthenticationDefender zum Wörterbuch REST_AUTH_SERIALIZERS in Ihrer settings.py unter dem Schlüssel LOGIN_SERIALIZER hinzu. Fügen Sie beispielsweise in Ihrer settings.py die folgende Zeile hinzu:

.. code-block:: python

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

Anpassung für Passwort-Reset-Formulare

defender kann für Djangos PasswordResetView angepasst werden, um zu viele Einsendungen zu verhindern.

Wir müssen einige neue Views erstellen, die Djangos eingebaute LoginView, PasswordResetView und PasswordResetConfirmView unterklassen – und diese Views dann in unserer urls.py als Ersatz für Djangos eingebaute Views verwenden.

Die Views blockieren basierend auf der E-Mail-Adresse, die im Passwort-Reset-Formular eingegeben wurde. Dies unterscheidet sich von der Standardimplementierung (die den Benutzernamen verwendet), daher müssen wir darauf achten, nach dem Anmelden und dem abgeschlossenen Passwort-Reset alles zu bereinigen.

.. 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

Django-Signale

django-defender sendet Signale, wenn ein Benutzername oder eine IP-Adresse blockiert wird. So richten Sie Signal-Empfängerfunktionen ein:

.. 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)

Tests ausführen

Tests können ausgeführt werden, nachdem Sie das Repository geklont und Django installiert haben, wie folgt:

.. code-block:: bash

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

Mit Code-Abdeckung:

.. code-block:: bash

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

Veröffentlichung

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

Tool herunterladen
DEFENDER_LOCK_OUT_BY_IP_AND_USERNAME
False
  • DEFENDER_DISABLE_IP_LOCKOUT\ : Boolean: Wenn True, wird die IP-Adresse des Benutzers nicht gesperrt, sondern nur der Benutzername. [Standard: False]

  • DEFENDER_DISABLE_USERNAME_LOCKOUT\ : Boolean: Wenn True, werden Benutzernamen nicht gesperrt, sondern nur IP-Adressen. [Standard: False]

  • DEFENDER_COOLOFF_TIME\ : Int: Wenn gesetzt, definiert es einen Zeitraum der Inaktivität, nach dem alte fehlgeschlagene Anmeldeversuche und Benutzername/IP-Sperren vergessen werden. Eine ganze Zahl wird als Anzahl von Sekunden interpretiert. Bei 0 verfallen weder die fehlgeschlagenen Anmeldeversuche noch die Benutzername/IP-Sperren. [Standard: 300\ ]

  • DEFENDER_ATTEMPT_COOLOFF_TIME\ : Int: Wenn gesetzt, überschreibt es den Zeitraum der Inaktivität, nach dem alte fehlgeschlagene Anmeldeversuche vergessen werden, der durch DEFENDER_COOLOFF_TIME gesetzt wird. Eine ganze Zahl wird als Anzahl von Sekunden interpretiert. Bei 0 verfallen die fehlgeschlagenen Anmeldeversuche nicht. [Standard: DEFENDER_COOLOFF_TIME\ ]

  • DEFENDER_LOCKOUT_COOLOFF_TIME\ : Int oder Liste: Wenn gesetzt, überschreibt es den Zeitraum der Inaktivität, nach dem Benutzername/IP-Sperren vergessen werden, der durch DEFENDER_COOLOFF_TIME gesetzt wird. Eine ganze Zahl wird als Anzahl von Sekunden interpretiert. Eine Liste von ganzen Zahlen wird als Anzahl von Sekunden für Benutzer interpretiert, wobei der Index der ganzen Zahl die Anzahl der vorherigen Sperren (bis zu einem Maximum) darstellt, die in den letzten DEFENDER_ACCESS_ATTEMPT_EXPIRATION Stunden aufgetreten sind. Wenn die Eigenschaft auf 0 oder [] gesetzt ist, verfällt die Benutzername/IP-Sperre nicht. [Standard: DEFENDER_COOLOFF_TIME\ ]

  • DEFENDER_LOCKOUT_TEMPLATE\ : String: [Standard: None\ ] Wenn gesetzt, gibt es ein Template an, das gerendert wird, wenn ein Benutzer gesperrt ist. Das Template erhält die folgenden Kontextvariablen:

    • cooloff_time_seconds\ : Die Abkühlzeit in Sekunden
    • cooloff_time_minutes\ : Die Abkühlzeit in Minuten
    • failure_limit\ : Die Anzahl der Fehler, bevor Sie gesperrt werden.
  • DEFENDER_USERNAME_FORM_FIELD\ : String: Der Name des Formularfelds, das Ihre Benutzernamen enthält. [Standard: username\ ]

  • DEFENDER_CACHE_PREFIX\ : String: Das Cache-Präfix für Ihre defender-Schlüssel. [Standard: defender\ ]

  • DEFENDER_LOCKOUT_URL\ : String: Die URL, zu der Sie weiterleiten möchten, wenn jemand gesperrt ist.

  • DEFENDER_REDIS_URL\ : String: Die Redis-URL für defender. [Standard: redis://localhost:6379/0\ ] (Beispiel mit Passwort: redis://:mypassword@localhost:6379/0\ )

  • DEFENDER_REDIS_PASSWORD_QUOTE\ : Boolean: Wenn Sonderzeichen im Redis-Passwort (wie '@') vorkommen, können wir das Passwort quotieren urllib.parse.quote("password!@#") und auf True setzen. [Standard: False\ ]

  • DEFENDER_REDIS_NAME\ : String: Der Name des Caches aus CACHES in Ihren Django-Einstellungen (z. B. "default"). Wenn gesetzt, wird DEFENDER_REDIS_URL ignoriert. [Standard: None\ ]

  • DEFENDER_STORE_ACCESS_ATTEMPTS\ : Boolean: Wenn Sie den Anmeldeversuch in der Datenbank speichern möchten, setzen Sie dies auf True. Bei False wird er nicht gespeichert. [Standard: True\ ]

  • DEFENDER_USE_CELERY\ : Boolean: Wenn Sie Celery verwenden möchten, um den Anmeldeversuch in der Datenbank zu speichern, setzen Sie dies auf True. Bei False wird er inline gespeichert. [Standard: False\ ]

  • DEFENDER_ACCESS_ATTEMPT_EXPIRATION\ : Int: Die Zeitspanne in Stunden, wie lange die Zugriffsversuchsdatensätze in der Datenbank aufbewahrt werden, bevor der Management-Befehl sie bereinigt. [Standard: 24\ ]

  • DEFENDER_GET_USERNAME_FROM_REQUEST_PATH\ : String: Der Importpfad der Funktion, die auf den Benutzernamen aus der Anfrage zugreift. Wenn Sie eine benutzerdefinierte Funktion verwenden möchten, um auf den Benutzernamen aus der Anfrage zuzugreifen und ihn zu verarbeiten, können Sie ihn hier angeben. [Standard: defender.utils.username_from_request\ ]