Das Problem mit den automatisierten Scans
Diese Botnetze agieren sprichwörtlich "blind". Sie scannen massenhaft das IPv4- und IPv6-Adressspektrum auf gut Glück ab. Sie wissen nicht, welche Applikationen tatsächlich auf dem Zielsystem laufen, sondern suchen automatisiert nach bekannten Schwachstellen in Content-Management-Systemen (wie WordPress), offenliegenden Konfigurationsdateien (.env) oder Fehlkonfigurationen.
Auch wenn ein einzelner HTTP-404-Fehler (Not Found) harmlos wirkt, summieren sich diese Scans schnell zu einem handfesten Problem:
- Ressourcenverbrauch: Jeder Request muss vom Apache-Webserver entgegengenommen, verarbeitet und protokolliert werden. Bei aggressiven Bot-Wellen bindet dies CPU und RAM. Zudem wird je nach Konfiguration für jede dieser Anfragen ein Datenbankeintrag in der sys_log Tabelle erzeugt, was zusätzliche Serverlast und Resourcenverbrauch zur Folge hat.
- Log-Spamming: Die Auswertung von Access- und Error-Logs wird massiv erschwert, wenn legitime Fehler in tausenden Zeilen von automatisiertem Müll untergehen.
- Erhöhtes Sicherheitsrisiko: Findet ein Bot zufällig eine ungeschützte Schwachstelle (z. B. eine vergessene Backup-Datei oder ein altes Skript), erfolgt die Kompromittierung vollautomatisch in Sekundenbruchteilen.
Beispiel: Logdateien auf einem Server, welcher eine TYPO3 Instanz hostet: Der Sicherheits-Grundsatz lautet: Hier gibt es absolut keinen legitimen Grund, warum ein Client auf Verzeichnisse wie /wp-content oder /wp-admin oder Dateien wie .env zugreifen sollte. Auch massenhaft Zugriff auf nicht existierende php Dateien sind ein eindeutiges Anzeichen dafür, dass der Server durch Bots gescannt wird. Solche Anfragen sind per Definition als böswillig einzustufen.
Beispieleinträge aus einem apache logfile
20.197.180.144 - - [22/Jun/2026:17:18:36 +0200] "GET /wp-admin/css/colors/midnight/about.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:37 +0200] "GET /bgymj.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:37 +0200] "GET /xx.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:37 +0200] "GET /file58.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:37 +0200] "GET /wp-links.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:38 +0200] "GET /solo1.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:38 +0200] "GET /flower.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:38 +0200] "GET /3.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:38 +0200] "GET /orm.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:38 +0200] "GET /ot.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:39 +0200] "GET /sixxis.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:39 +0200] "GET /2P.update.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:39 +0200] "GET /f3027655e14136.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:39 +0200] "GET /25d653587fdfd1.php HTTP/1.1" 401 381 "-" "-"
20.197.180.144 - - [22/Jun/2026:17:18:39 +0200] "GET /f35.php HTTP/1.1" 401 381 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:05 +0200] "GET /wp-admin/ HTTP/1.1" 301 245 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:05 +0200] "GET /wp-admin/ HTTP/1.1" 401 381 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:11 +0200] "GET /wp-admin/wp.php HTTP/1.1" 301 251 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:12 +0200] "GET /wp-admin/wp.php HTTP/1.1" 401 381 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:23 +0200] "GET /wp-admin/js/ HTTP/1.1" 301 248 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:23 +0200] "GET /wp-admin/js/ HTTP/1.1" 401 381 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:27 +0200] "GET /wp-admin/js/index.php HTTP/1.1" 301 257 "-" "-"
20.104.244.165 - - [23/Jun/2026:02:42:27 +0200] "GET /wp-admin/js/index.php HTTP/1.1" 401 381 "-" "-"
Präventionsmöglichkeiten: Proaktives Blockieren auf Netzwerkebene
Um die Last vom Webserver zu nehmen und Angreifer frühzeitig zu isolieren, reicht das Ausliefern einer Fehlerseite nicht aus. Die effektivste Lösung ist das dynamische Blockieren auf Netzwerkebene (Firewall) mithilfe eines Log-Analysators wie Fail2ban.
Fail2ban überwacht die Logfiles in Echtzeit, erkennt verdächtige Verhaltensmuster anhand von regulären Ausdrücken (Regex) und sperrt die Verursacher-IP temporär via UFW (Uncomplicated Firewall), iptables oder nftables.
Konkrete Umsetzung: Filter und Jail-Konfiguration
1. Der Filter: Beispiel in /etc/fail2ban/filter.d/apache-phpscan.conf
Dieser Filter sucht im Access-Log nach zwei Mustern:
- Aufrufe von PHP-Dateien, die fehlschlagen (HTTP-Statuscode ungleich 200, z. B. 403, 404, 301, 302).
- Explizite Zugriffe auf hochsensible Pfade (WordPress-Strukturen, Umgebungsvariablen, Exchange Autodiscover), unabhängig vom gelieferten Statuscode. Da wir diese Pfade nicht bedienen, wird jeder Aufruf als bösartiger Scan eingestuft. Die Regular Expressions müssen möglicherweise jeweils an das Logformat angepasst werden, vor allem wenn ein anderer Webserver / Proxy wie nginx modifizierte Logdateien verwendet werden.
[Definition]
failregex = ^<HOST> - - \[.*\] "(?:GET|POST|HEAD) \S*\.php\S* HTTP/\d+(?:\.\d+)*" (?:30[12]|40[0134])
^<HOST> - - \[.*\] "(?:GET|POST|HEAD) /+wp-content\S* HTTP/\d+(?:\.\d+)*" \d+
^<HOST> - - \[.*\] "(?:GET|POST|HEAD) /+wp-admin\S* HTTP/\d+(?:\.\d+)*" \d+
^<HOST> - - \[.*\] "(?:GET|POST|HEAD) /+wp-includes\S* HTTP/\d+(?:\.\d+)*" \d+
^<HOST> - - \[.*\] "(?:GET|POST|HEAD) /+\.env\S* HTTP/\d+(?:\.\d+)*" \d+
^<HOST> - - \[.*\] "(?:GET|POST|HEAD) (?i)/+autodiscover/autodiscover\.xml\S* HTTP/\d+(?:\.\d+)*" \d+
ignoreregex =
2. Das Jail: /etc/fail2ban/jail.d/apache-phpscan.local
Das zugehörige Jail definiert die Log-Pfade und die Härte der Sperre. In unserem Beispiel: Erreicht eine IP innerhalb von 10 Sekunden (findtime) mehr als 5 Treffer (maxretry), wird sie für 5 Stunden (bantime = 18000) komplett über einen Eintrag in der Firewall ausgesperrt.
[apache-phpscan]
enabled = true
port = http,https
filter = apache-phpscan
logpath = /var/log/httpd/access_log
/var/log/httpd/another_access_log
backend = auto
action = iptables-multiport[port="http,https"]
findtime = 10
maxretry = 5
bantime = 18000
Fazit & Best Practice
Mit minimalem Konfigurationsaufwand sorgt dieses Fail2ban-Setup dafür, dass automatisierte Scanner nach wenigen Requests gegen eine digitale Wand laufen. Das schont die Ressourcen des Apache-Webservers und hält die Logfiles sauber.
Wichtiger Praxistipp vor dem Livegang: Teste deine Konfiguration immer vorab mit dem integrierten Fail2ban-Tool gegen deine echten Logfiles, um sicherzustellen, dass die regulären Ausdrücke fehlerfrei matchen:
fail2ban-regex /var/log/httpd/access_log /etc/fail2ban/filter.d/apache-phpscan.conf
Sonstige hilfreiche Hinweise:
# Liste aller durch den Fail2Ban Filter erzeugen Einträge
iptables -L f2b-apache-phpscan -n -v
# Status und Statistiken des Fail2Ban Filters ausgeben
fail2ban-client status apache-phpscan