www.lowcodeapp.de - Beschleunigung der digitalen Transformation mit Open Source Low-Code Development.

Logins in TYPO3 absichern

Tastatur auf dem ein Sicherheitsschloss liegt.

Es gibt viele Sicherheitsmechanismen um Logins in TYPO3 abzusichern, egal ob im Frontend oder Backend. So können Passwörter bei Logins RSA verschlüsselt übermittelt werden. Wenn ein Loginversuch fehlschlägt dann gibt es außerdem eine Verzögerung von 5 Sekunden bis das nächste Login angezeigt wird, um evtl. Angriffe zu verlangsamen. Das sind schon recht wirksame Features, um mehr Sicherheit zu erhalten und Angriffe zu erschweren. Aber geht noch mehr?

Es muss natürlich klar sein dass eine 100%ige Sicherheit nie möglich ist. Dennoch sollten Websitebetreiber immer bemüht sein, Angreifern so viele Hürden wie möglich in den Weg zu legen. Dabei muss selbstverständlich abgewogen werden ob der Nutzen den Aufwand rechtfertigt. Daher möchten wir hier einen weiteren Sicherheitsmechanismus vorstellen, welcher sich mit relativ wenig Aufwand integrieren lässt und einen guten Schutz vor Bruteforce Attacken auf Logins bietet. Mit diesem zusätzlichen Schutz müssen Angreifer bereits deutlich mehr Aufwand betreiben, um eine erfolgreiche Bruteforce Attacke auf ein Login zu fahren.

Die Idee ist, Nutzerdaten bzw. IPs nach einer bestimmten Anzahl von fehlerhaften Loginversuchen (sowohl im Frontend als auch im Backend), eine Zeit lang für weitere Logins zu sperren. So sollte es 3 Kriterien geben, um fehlerhafte Loginversuche zu erkennen. Wenn ein Nutzername, egal von welchem Nutzer/IP, zu oft zu fehlerhaften Logins geführt hat, dann sollte dieser Nutzername für weitere Loginversuche für eine Zeit gesperrt werden. Gleiches gilt für das Passwort, vorausgesetzt es wird im Klartext übermittelt. Wenn es z.B. RSA verschlüsselt übertragen wird, ist eine Überwachung nicht möglich da das verschlüsselte Passwort jedes mal anders aussieht. Außerdem sollte eine IP, welche unabhängig vom verwendeten Nutzername und Passwort, ein fehlerhaftes Login verursacht, auch nach einer bestimmten Anzahl von fehlerhaften Versuchen gesperrt werden.
 
Nun kann versucht werden, eine eigene Lösung zu programmieren. Oder es wird ein System genutzt, das erprobt ist und genau diese Anforderungen abdeckt. Möglich ist das mit dem Apache Modul ModSecurity  (gibt es auch für andere Server), welches sehr mächtig ist und noch viel mehr Features bietet (auch die sind einen Blick wert aber hier nicht das Thema).
Dazu muss ModSecurity einfach installiert werden (Anleitung für Linux und Windows). Anschließend sollte ModSecurity in der Hauptkonfigurationsdatei (unter Linux z.B. /etc/modsecurity/modsecurity.conf) global deaktiviert werden, falls es nur für den Bruteforce Schutz genutzt wird bzw. zumindest aber die default Regeln auskommentiert werden im TYPO3 Kontext, da diese zu ungewollten Problemen führen können. Die Datei könnte dann wie folgt aussehen:

LoadModule security2_module modules/mod_security2.so
<IfModule !mod_unique_id.c>
    LoadModule unique_id_module modules/mod_unique_id.so
</IfModule>
<IfModule mod_security2.c>
    ### gloabl deaktiviert im gegensatz zum Original
    SecRuleEngine Off
    SecRequestBodyAccess Off
    SecRule REQUEST_HEADERS:Content-Type "text/xml" \
         "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
    SecRequestBodyLimit 13107200
    SecRequestBodyNoFilesLimit 131072
    SecRequestBodyInMemoryLimit 131072
    SecRequestBodyLimitAction Reject
    SecRule REQBODY_ERROR "!@eq 0" \
    "id:'200001', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
    SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
    "id:'200002',phase:2,t:none,log,deny,status:44,msg:'Multipart request body \
    failed strict validation: \
    PE %{REQBODY_PROCESSOR_ERROR}, \
    BQ %{MULTIPART_BOUNDARY_QUOTED}, \
    BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
    DB %{MULTIPART_DATA_BEFORE}, \
    DA %{MULTIPART_DATA_AFTER}, \
    HF %{MULTIPART_HEADER_FOLDING}, \
    LF %{MULTIPART_LF_LINE}, \
    SM %{MULTIPART_MISSING_SEMICOLON}, \
    IQ %{MULTIPART_INVALID_QUOTING}, \
    IP %{MULTIPART_INVALID_PART}, \
    IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
    FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"

    ### im Gegensatz zum Original das deny entfernt
    SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \
    "id:'200003',phase:2,t:none,log,status:44,msg:'Multipart parser detected a possible unmatched boundary.'"

    SecPcreMatchLimit 1000
    SecPcreMatchLimitRecursion 1000

    SecRule TX:/^MSC_/ "!@streq 0" \
            "id:'200004',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'"

    SecResponseBodyAccess Off
    SecDebugLog /var/log/apache2/modsec_debug.log
    SecDebugLogLevel 0
    SecAuditEngine RelevantOnly
    SecAuditEngine Off
    SecAuditLogRelevantStatus "^(?:5|4(?!04))"
    SecAuditLogParts ABIJDEFHZ
    SecAuditLogType Serial
    SecAuditLog /var/log/apache2/modsec_audit.log
    SecArgumentSeparator &
    SecCookieFormat 0
    SecTmpDir /tmp/
    SecDataDir /tmp/
    SecResponseBodyMimeType text/plain text/html text/xml
    SecResponseBodyLimit 524288
</IfModule>

Wenn das erledigt ist, müssen noch in der V-Host Konfiguration der gewünschten Domain die notwendigen Regeln für den Bruteforce Schutz eingefügt werden. Die folgenden Regeln sind für eine FE Login Seite (login.html) und das BE Login in einem TYPO3 Projekt, wobei das FE Login mit t3users (ab Version 1.0.7) erfolgen muss. t3users sendet einen eigenen Response Header bei fehlgeschlagenen Logins, auf welchen reagiert wird. Wenn der Einsatz von t3users keine Option ist, können auch andere Kriterien herangezogen werden. (z.B. im HTML Quellcode einen String/Kommentar einfügen, mit welchem der fehlerhafte Login erkannt wird analog zum Backend Login).
Dabei wird folgendes erreicht: Wenn innerhalb 1h das Login 15 mal von einer IP unabhängig vom Nutzername/Passwort und/oder 5 mal mit demselben Nutzernamen/Passwort unabhängig von der IP fehlschlägt, wird das Login für die IP, das Passwort oder den Nutzername 10min gesperrt. Im Falle einer Sperrung werden abhängig vom Grund verschiedene Fehlerseiten angezeigt. Die Überwachung des Passwort im Backend Login funktioniert nicht auf Grund der RSA Verschlüsselung.

<VirtualHost 127.0.0.1:80>
    ...

    ### BRUTE FORCE PROTECTION START
    #
    # TYPO3 Backend Login Bruteforce Schutz
    # wenn innerhalb 1h das Login 15 mal von einer IP und/oder 5 mal mit demselben Nutzernamen
    # fehlschlägt, wird das Login für 10min gesperrt nach dem nächsten fehlerhaften Versuch.
    # da das Passwort mit RSA übermittelt wird, ist es jedes mal anders und kann damit nicht überwacht werden.
    #
    <Location '/typo3/index.php'>
        # gzip Komprimierung muss deaktiviert werden, da ansonsten der Inhalt des Body nicht korrekt
        # verarbeitet werden kann
        SetEnv no-gzip 1

        # SecruleEngine aktivieren (http://typo3.org/waf.txt
        # deaktiviert ModSecurity für das gesamte Backend)
        SecRuleEngine On
        # Wir untersuchen den Response Body (enthält die , um festzustellen ob ein fehlgeschlagenes Login vorliegt
        SecResponseBodyAccess On
        # Wir brauchen Zugriff auf die GET und POST Parameter
        SecRequestBodyAccess On

        # IP inkl. des User-Agent ermitteln, falls die IP aus einem Netzwerk stammt und damit mehrere Nutzer vorhanden sein könnten pro IP
        SecRule REQUEST_HEADERS:User-Agent "^(.*)$" \
          "id:'900018', \
          phase:1, \
          t:none,t:sha1,t:hexEncode, \
          setvar:tx.ua_hash=%{matched_var}, \
          nolog, \
          pass"

        SecAction phase:2,nolog,pass,initcol:IP=%{REMOTE_ADDR}_%{tx.ua_hash},id:5000100

        # IP Block durchführen
        SecRule IP:bf_block "@eq 1" \
            "phase:2,deny,redirect:/ip-gesperrt.html,id:5000101"

        # den gesendeten Nutzername speichern
        SecAction phase:2,nolog,pass,initcol:USER=%{ARGS.username},id:5000102

        # Nutzername Block durchführen
        SecRule USER:bf_block "@eq 1" \
            "phase:2,deny,redirect:/nutzer-gesperrt.html,id:5000103"

        # Prüfen ob es einen fehlerhaften Loginversuch gab und die Counter hochsetzen für IP und Nutzername
        SecRule RESPONSE_BODY "LOGIN_ERROR###" \
            "phase:5,t:none,nolog,pass, \
            setvar:IP.bf_counter=+1, \
            setvar:USER.bf_counter=+1, \
            expirevar:IP.bf_counter=3600, \
            expirevar:USER.bf_counter=3600,id:5000106"

        # Wer sich 15mal falsch mit einer IP
        # anmeldet, wird beim 16. Versuch gesperrt
        SecRule IP:bf_counter "@gt 14" \
            "phase:5,pass,t:none,nolog, \
            setvar:IP.bf_block, \
            setvar:!IP.bf_counter, \
            expirevar:IP.bf_block=600,id:5000107"

        # Wenn ein Nutzername 5mal zu einem fehlerhaften Login führt, wird
        # dieser beim 6. Versuch gesperrt
        SecRule USER:bf_counter "@gt 4" \
            "phase:5,t:none,pass,nolog, \
            setvar:USER.bf_block, \
            setvar:!USER.bf_counter, \
            expirevar:USER.bf_block=600,id:5000108"
    </Location>

    #
    # TYPO3 FE Login Bruteforce Schutz
    # wenn innerhalb 1h das Login 15 mal von einer IP und/oder 5 mal mit demselben Nutzernamen/Passwort
    # fehlschlägt, wird das Login für 10min gesperrt nach dem nächsten fehlerhaften Versuch
    #
    <LocationMatch '.*login\.html'>
        # SecruleEngine aktivieren 
        SecRuleEngine On

        # Wir brauchen Zugriff auf die GET und POST Parameter
        SecRequestBodyAccess On

        # Wir untersuchen den Response Body (enthält die , um festzustellen ob ein fehlgeschlagenes Login vorliegt
        SecResponseBodyAccess On

        # P inkl. des User-Agent ermitteln, falls die IP aus einem Netzwerk stammt und damit mehrere Nutzer vorhanden sein könnten pro IP
        SecRule REQUEST_HEADERS:User-Agent "^(.*)$" \
          "id:'900018', \
          phase:1, \
          t:none,t:sha1,t:hexEncode, \
          setvar:tx.ua_hash=%{matched_var}, \
          nolog, \
          pass"

        SecAction phase:2,nolog,pass,initcol:IP=%{REMOTE_ADDR}_%{tx.ua_hash},id:5000100

        # IP Block durchführen
        SecRule IP:bf_block "@eq 1" \
            "phase:2,deny,redirect:/ip-gesperrt.html,id:5000101"

        # den gesendeten Nutzername speichern
        SecAction phase:2,nolog,pass,initcol:USER=%{ARGS.user},id:5000102

        # Nutzername Block durchführen
        SecRule USER:bf_block "@eq 1" \
            "phase:2,deny,redirect:/nutzer-gesperrt.html,id:5000103"

        # das gesendete Passwort speichern
            SecAction phase:2,nolog,pass,initcol:RESOURCE=%{ARGS.pass},id:5000104

        # Passwort Block durchführen
        SecRule RESOURCE:bf_block "@eq 1" \
            "phase:2,deny,redirect:/passwort-gesperrt.html,id:5000105"

        # Prüfen ob es einen fehlerhaften Loginversuch gab und die Counter hochsetzen für IP, Nutzername und Passwort
        SecRule RESPONSE_HEADERS:Login "@streq -1" \
            "phase:5,t:none,nolog,pass, \
            setvar:IP.bf_counter=+1, \
            setvar:USER.bf_counter=+1, \
            setvar:RESOURCE.bf_counter=+1, \
            expirevar:IP.bf_counter=3600, \
            expirevar:RESOURCE.bf_counter=3600, \
            expirevar:USER.bf_counter=3600,id:5000106"

        # Wer sich 15mal falsch mit einer IP
        # anmeldet, wird beim 16. Versuch gesperrt
        SecRule IP:bf_counter "@gt 14" \
            "phase:5,pass,t:none,nolog, \
            setvar:IP.bf_block, \
            setvar:!IP.bf_counter, \
            expirevar:IP.bf_block=600,id:5000107"

        # Wenn ein Nutzername 5mal zu einem fehlerhaften Login führt, wird
        # dieser beim 6. Versuch gesperrt
        SecRule USER:bf_counter "@gt 4" \
            "phase:5,t:none,pass,nolog, \
            setvar:USER.bf_block, \
            setvar:!USER.bf_counter, \
            expirevar:USER.bf_block=600,id:5000108"

        # Wenn ein Passwort 5mal zu einem fehlerhaften Login führt, wird
        # dieser beim 6. Versuch gesperrt
        SecRule RESOURCE:bf_counter "@gt 4" \
            "phase:5,t:none,pass,nolog, \
            setvar:RESOURCE.bf_block, \
            setvar:!RESOURCE.bf_counter, \
            expirevar:RESOURCE.bf_block=600,id:5000109"
    </LocationMatch>
    ### BRUTE FORCE PROTECTION END
</VirtualHost>

Wenn das integriert ist, muss es natürlich auch die konfigurierten Fehlerseiten geben. Das wären dann im webroot /nutzer-gesperrt.html, /passwort-gesperrt.html und /ip-gesperrt.html. That's it.

Um das ganze zu debuggen, können die nolog Anweisungen zu log gemacht werden. Anschließend ist im Apache Error Log sichtbar wenn z.B. ein fehlerhafter Loginversuch erkannt wurde oder eine Sperrung vorgenommen wird/vorliegt. Mit dem Debug und Audit Log, gibt es noch deutlich mehr Analysemöglichkeiten. Wie diese zu aktivieren/konfigurieren sind, ist teilweise selbsterklärend, kann aber auch der Dokumentation von ModSecurity entnommen oder schnell bei Google gefunden werden.

Zusammen mit der Verzögerung des nächsten Loginversuchs besteht so ein grundlegender und wirksamer Schutz vor Bruteforce Attacken bei Logins in TYPO3, der die meisten Angreifer abschrecken dürfte. Diese Mechanismen lassen sich auch sehr einfach in andere Systeme übertragen, besonders der Einsatz von ModSecurity.

Hinweis: Auch diese Lösung ist nicht frei von Nachteilen. Angreifer könnten diesen Mechanismus nun auch verwenden, um eine Seite zu sabotieren. Vorausgesetzt es sind verschiedene Nutzernamen bekannt, dann könnte mit diesen einfach fehlerhafte Logins erzeugt werden bis zur Sperrung, womit der rechtmäßige Nutzer sich auch nicht mehr einloggen kann. Das Risiko dafür sollte aber relativ gering sein.
 

ZURÜCK