Devlog
Devlog 29. Oktober 2026

🔒Wie ich Scanner aussperre: Fail2ban mit einem Treffer

Über 9.000 Angriffsversuche in zwei Monaten – und warum eine einzige verdächtige Anfrage für eine Woche Sperre reicht

Jeder Server wird gescannt: .env-Suche, PHP-Shells, SQL-Injection. So sperre ich die Angreifer auf meinen Servern und bei Scitch automatisch aus – mit eigenen Fail2ban-Filtern, getestet gegen echte Logs.

Wer einen Server ins Internet stellt, wird gescannt. Nicht irgendwann, sondern ab der ersten Minute. Für meine Seiten und für Scitch, mein Live-Reaktionsspiel, habe ich mir deshalb angesehen, was da eigentlich ankommt, und die Angreifer automatisch aussperren lassen. Hier ist, wie das aufgebaut ist.

Was in den Logs steht

Ich habe die Zugriffslogs einer meiner Seiten über knapp zwei Monate ausgewertet: rund 483.000 Anfragen, davon über 9.000 eindeutige Angriffsversuche. Durchgekommen ist keiner, aber das Muster ist immer dasselbe:

  • PHP-Shell-Versuche: eval-stdin.php aus alten PHPUnit-Versionen, ThinkPHP invokefunction, PHP-CGI mit allow_url_include, Webshells wie alfa.php oder wso.php.
  • Geheimnis-Suche: /.env, /.env.production, /.git/config, /.aws/credentials, wp-config.php, SQL-Dumps.
  • SQL-Injection: Einmal hat jemand mit einem sqlmap-artigen Werkzeug über 130 Varianten von UNION SELECT und EXTRACTVALUE auf einen einzigen Parameter geschossen.

Das Entscheidende: Kein echter Besucher schickt jemals so eine Anfrage. Wer /.env abruft, ist kein Mensch, der sich verklickt hat. Genau das macht die Sperre so einfach.

Die Idee: ein Treffer reicht

Fail2ban liest Logdateien mit, sucht per Regex nach Mustern und sperrt die IP über die Firewall. Klassisch nutzt man das gegen Passwort-Raten: fünf Fehlversuche in zehn Minuten, dann eine Stunde Pause.

Bei Scannern ist das zu nachsichtig. Ein Scanner probiert in den ersten Sekunden Dutzende Pfade durch, und mit fünf Freiversuchen hat er seine Liste fast durch. Deshalb gilt bei mir: ein Treffer, eine Woche Sperre, alle Ports. Wer wiederkommt, wird über bantime.increment jedes Mal länger gesperrt.

Der Filter

Der Filter läuft über die Apache-Zugriffslogs aller Domains auf dem Server. Hier die PHP-Shell-Regel:

[Definition]
failregex = ^<HOST> \S+ \S+ \[[^]]*\] "[A-Z]+ [^"]*(?i:allow_url_include|auto_prepend_file|php://(?:input|filter)|eval-stdin\.php|invokefunction|/cgi-bin/php|/(?:alfa|wso|c99|r57|shell|webshell|bypass)\.php)[^"]*"
            ^<HOST> \S+ \S+ \[[^]]*\] "[^"]*" \d+ \S+ "[^"]*" "[^"]*(?i:php2shell)
ignoreregex =

Die zweite Zeile prüft den User-Agent. Manche Exploit-Werkzeuge verraten sich dort selbst.

Für Scanner gibt es einen zweiten Filter, der nach .env, .git/, .aws/, .ssh/, wp-config.php, *.sql und typischen SQL-Injection-Mustern sucht. Bei .env muss man aufpassen: /.env und /.env.local sollen treffen, /environment nicht.

failregex = ^<HOST> \S+ \S+ \[[^]]*\] "[A-Z]+ [^" ?]*(?i:/\.env(?:[.\-_][^/" ?]*)?(?=[ ?])|/\.git/|/\.aws/|/\.ssh/|/id_rsa|/wp-config\.php|\.sql(?:\.gz|\.zip)?(?=[ ?]))

Eine Falle, die mich Zeit gekostet hat: Fail2ban liest die Konfiguration mit Pythons configparser. Ein % in der Regex, etwa für URL-kodierte Zeichen wie %20 oder %27, wird dort als Platzhalter verstanden. Man muss es als %% schreiben, sonst startet der Filter nicht oder matcht etwas anderes als gedacht.

Die Jail

[php-shell]
enabled  = true
filter   = php-shell
backend  = auto
logpath  = /var/www/vhosts/system/*/logs/access_log
           /var/www/vhosts/system/*/logs/access_ssl_log
maxretry = 1
findtime = 10m
bantime  = 1w
bantime.increment = true
ignoreip = 127.0.0.1/8 ::1 <eigene IPs>
action   = iptables-allports[name=php-shell, protocol=all]

ignoreip ist Pflicht: Die eigene IP und die des Servers dürfen nie gesperrt werden. Sonst sperrt man sich beim ersten Test selbst aus.

Vor dem Scharfschalten testen

Eine Sperre mit maxretry = 1 ist gnadenlos. Wenn die Regex einmal zu viel trifft, fliegen echte Besucher raus. Deshalb lasse ich jeden Filter vorher gegen die echten Logs der letzten Monate laufen:

fail2ban-regex /var/www/vhosts/system/beispiel.at/logs/access_ssl_log \
    /etc/fail2ban/filter.d/php-shell.conf --print-all-matched

Dann gehe ich die Treffer durch: Sind das alles Scanner? Ist meine eigene IP dabei? Steckt eine normale Seite dazwischen? Bei mir hätten die beiden Filter über vier Monate 76 IPs wegen PHP-Shell-Versuchen und 234 Scanner gesperrt, und kein einziger echter Seitenaufruf war dabei. Erst dann wird die Jail aktiviert.

Benachrichtigung aufs Handy

Bei Scitch bekomme ich für jede PHP-Shell-Sperre eine Push-Nachricht über Pushover: mit IP, Domain, den letzten Anfragen und einem Link auf AbuseIPDB. Dafür reicht eine eigene Fail2ban-Aktion, die ein kleines Skript aufruft:

[Definition]
actionban = /usr/local/sbin/ban-push <name> <ip> <bantime>

Das Skript bekommt nur Jail-Name, IP und Sperrdauer. Die Logzeilen holt es sich selbst, damit keine vom Angreifer kontrollierten Inhalte durch die Shell laufen. Für die reinen Scanner (.env & Co.) habe ich die Push-Nachricht wieder abgeschaltet. Das sind so viele, dass das Handy sonst nicht mehr still wäre.

Was es gebracht hat

  • Scanner sehen nach der ersten verdächtigen Anfrage nur noch eine geschlossene Tür und probieren den Rest ihrer Liste gar nicht mehr.
  • Die Logs sind deutlich ruhiger, echte Fehler fallen wieder auf.
  • Die Exploit-Versuche gegen die kritische PHP-Lücke aus dem Oktober landen seither nach der ersten Anfrage in der Sperre.

Fail2ban ersetzt keine Updates. Gepatcht wird trotzdem sofort. Aber es nimmt den automatisierten Angriffen die Zeit, die sie zum Ausprobieren brauchen, und das ist bei einem kleinen Server schon sehr viel.

Fragen oder eigene Erfahrungen mit Fail2ban? Schreib's in die Kommentare.

#Fail2ban #Linux #Security #Server #Plesk #Scitch #Devlog

Scitch ansehen

scitch.com

Wie findest du diesen Beitrag?

Kommentar hinterlassen

Mit dem Absenden stimmst du zu, dass dein Name und Kommentar gespeichert werden. Es werden keine weiteren personenbezogenen Daten erhoben. Datenschutz

Impressum · Datenschutz ·

© 2026 derCoder