Mehr Schutz, weniger Fehlalarme: Mein Weg mit BunkerWeb
Ausnahmen so eng wie möglich, Alarme so gezielt wie nötig. Ein Blick hinter die Kulissen von BunkerWeb
Im ersten Teil ging es darum, BunkerWeb überhaupt vor meinen Diensten zu platzieren. Das war der leichtere Teil. Danach begann die eigentliche Arbeit: Die WAF blockierte anfangs Dinge, die harmlos waren, und protokollierte Dinge, die niemand lesen wollte. In diesem Artikel gehe ich den Weg noch einmal durch, mit den Konfigurationen und Logzeilen, die dabei entstanden sind.
Inhaltsverzeichnis
Warum ich nicht einfach alles abschalte
Am Anfang war die Versuchung groß. Eine Regel schlägt an, ein Beitrag wird nicht gespeichert, und die schnellste Lösung ist ein Schalter, der die ganze WAF für den Dienst abschaltet. Ich habe das bei zwei Diensten kurz getestet, was sich aber am Ende doch deutlich unsicherer anfühlte.
Ab diesem Zeitpunkt habe ich mir für jede Ausnahme drei Fragen gestellt. Welcher Pfad ist betroffen? Welches Feld oder welcher Header löst die Regel aus? Was passiert mit allen anderen Anfragen, wenn die Ausnahme greift? Eine Ausnahme, die für den ganzen Server gilt, beantwortet die dritte Frage schlecht. Eine Ausnahme für ein einzelnes Feld auf einem einzelnen Pfad beantwortet sie gut.
Das OWASP Core Rule Set arbeitet allgemein, weil es nicht wissen kann, was auf meinem Server läuft. Die Dokumentation auf coreruleset.org erklärt die Regelgruppen, und die Security-Tuning-Seite von BunkerWeb zeigt, wie man Regeln gezielt anpasst.
Der Blog-Editor, der wie ein Angriff aussah
Der Ghost-Editor schickt beim Speichern ein JSON-Objekt mit dem kompletten Beitrag. Mein Beitrag enthielt ein kleines Skript für das Inhaltsverzeichnis und einige CSS-Blöcke. Für die WAF war das eine Mischung aus HTML, JavaScript und Text, und die Anomalie-Bewertung kletterte schnell über den Schwellwert.

Die Logzeile sah sinngemäß so aus:
2026/10/10 10:52:38 [error] ModSecurity: Access denied with code 403 (phase 2).
Matched "Operator `Ge' with parameter `6' against variable `TX:BLOCKING_INBOUND_ANOMALY_SCORE'
[id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 50)"]
[hostname "my-blog-domain"] [uri "/blog/api/michael/posts/<post-id>/"]
client: 203.0.113.10, server: my-blog-domain, request: "PUT /blog/api/michael/posts/<post-id>/ HTTP/2.0"
Die Regel 949110 ist nur die Auswertung am Ende. Die eigentlichen Treffer stehen in den Zeilen davor, mit derselben unique_id. Wer nur die letzte Zeile liest, versteht den Fehler nicht.
Die Lösung bestand aus zwei Teilen. Der erste Teil war der wichtigere: Skript und CSS habe ich aus dem Beitrag herausgenommen und über die Code-Injection-Einstellungen von Ghost eingebunden. Ein Beitrag mit reinem Text sieht für die WAF unverdächtig aus, und das Problem entstand nur durch meine eigene Konstruktion.
Der zweite Teil war eine enge Ausnahme für genau dieses Feld im Admin-Bereich:
# Ghost-Admin: Beitragsinhalt beim Speichern (nur Schreibzugriffe im Admin-Bereich)
# Ausnahme nur für das Inhaltsfeld, nicht für den gesamten Dienst.
SecRule REQUEST_URI "@beginsWith /blog/api/michael/posts/" \
"id:1000001,\
phase:1,\
pass,\
nolog,\
ctl:ruleRemoveTargetById=941110;ARGS:json.posts.array_0.lexical"
Die vollständige Liste der Regeln habe ich aus den Logs gezogen, hier steht nur ein Ausschnitt. Jede Regel-ID bekam einen eigenen Eintrag, sodass ich später nachvollziehen kann, welche Regel für welchen Fall getriggert wurde. Der Admin-Bereich ist sowieso durch Anmeldung geschützt, und die Anzahl der Nutzer ist überschaubar. Trotzdem ist er eine Angriffsfläche, falls jemand z.B. meine Zugangsdaten stiehlt. Deshalb ist die Ausnahme bewusst schmal gehalten.
Die Notizen-App und das Wort Fish
Meine Nextcloud-Notizen haben ein ähnliches Problem verursacht. Unter Anderem enthielt eine Einkaufsliste mit Pizza, Maultaschen und Schnitzel das Wort "Fish". Eine Regel für Shell-Befehle hat es als Befehl gewertet und das Speichern blockiert. Die Einkaufsliste war kein Angriff, und die Regel hatte in diesem Kontext schlicht falsch gelegen.
Die Ausnahme gilt zum Beispiel nur für das Feld mit dem Notiztext und nur für den Notizen-Endpunkt:
# Nextcloud Notes: Inhalt einer Notiz beim Speichern
SecRule REQUEST_URI "@beginsWith /index.php/apps/notes/api/" \
"id:1000003,\
phase:1,\
pass,\
nolog,\
ctl:ruleRemoveTargetById=932235;ARGS:json.content"
Nach der Änderung habe ich eine Notiz mit genau diesem Wort gespeichert und das Log kontrolliert. Keine Blockierung, keine Warnung. Eine Notiz mit einem echten Shell-Konstrukt würde dagegen weiterhin von der Regel geprüft. Das war der Punkt, an dem ich verstanden habe, dass Tests mit echten Daten mehr bringen als jede Theorie.
WebDAV und der falsche Content-Type
Beim Auflisten von Ordnern im Nextcloud-Web-Frontend und im Desktop-Client wird ein Content-Type gesendet, den eine Regel nicht erlaubt. Auch hier habe ich die Ausnahme auf den WebDAV-Pfad begrenzt. Die genaue Konfiguration lasse ich bewusst weg, weil sie in genau diesem Pfad eine Prüfung abschaltet und ich nicht beschreiben möchte, wo sie sich gezielt umgehen lässt. Wer ähnliches vorhat, sollte die Regel für den eigenen Dienst analysieren und nicht meine Lösung kopieren.
Der Unterschied zu den anderen Fällen ist wichtig. Beim Notizen-Endpunkt habe ich ein Feld ausgenommen, bei WebDAV eine Prüfung für einen Pfad. Die zweite Variante ist grober, und ich habe sie nur eingeführt, nachdem die Logs gezeigt hatten, dass es keine andere saubere Möglichkeit gibt.
Ein Accept-Header mit Parameter
Eine Fediverse-Anwendung auf einem meiner Dienste schickt einen Accept-Header mit einem Parameter, den eine Regel zur Protokollprüfung beanstandet. Der Header ist harmlos, und ich habe die Ausnahme auf lesende Zugriffe begrenzt. Schreibende Zugriffe bleiben vollständig geprüft. Diese Beschränkung auf eine Methode war der Punkt, an dem ich sorgfältig abgewogen habe, ob sie wirklich nötig ist, oder ob ich die Regel nur aus Bequemlichkeit abschalte.
DetectionOnly, On und der Schalter, den ich zu früh umlegen wollte
ModSecurity kennt im BunkerWeb-Umfeld zwei Modi. Im Modus DetectionOnly schreibt die WAF Warnungen, blockiert aber nichts. Im Modus On blockiert sie Anfragen mit dem Statuscode 403. Die Einstellung gehört zum Dienst, und man sieht sie in der Oberfläche oder in der Konfiguration:
MODSECURITY_SEC_RULE_ENGINE=DetectionOnly

Am Anfang habe ich Tests gemacht, bei denen ich eine Anfrage schickte, keine Blockierung sah und mich wunderte. Die Antwort stand in der Konfiguration. Mein Vorgehen danach war langsamer, aber sicherer. Zuerst habe ich über mehrere Tage im Modus DetectionOnly beobachtet, welche Regeln im normalen Betrieb anschlagen. Erst danach habe ich die Ausnahmen eingetragen und den Dienst auf On gestellt. Den Umstieg habe ich mit einem Dienst gemacht, den nur ich benutze.
Die Blockierung sieht im Log so aus:
2026/10/10 10:52:38 [error] ModSecurity: Access denied with code 403 (phase 2).
[id "949110"] [hostname "my-cloud-domain"] [uri "/index.php/apps/..."] client: 203.0.113.10
Diese Zeile ist für Wazuh die wichtigste. Sie bedeutet nicht nur, dass eine Regel angeschlagen hat, sondern dass die Anfrage tatsächlich abgewehrt wurde.
Tests ohne Kollateralschaden
Für meine Tests habe ich mit wenigen, harmlosen Anfragen gearbeitet, an meine eigenen Dienste und mit Pausen dazwischen. Ein Testskript, das hunderte Anfragen pro Minute schickt, belastet nur den eigenen Server und erzeugt Logs, die das eigentliche Problem verdecken.

Dabei habe ich eine Lektion über BADBEHAVIOR gelernt. BunkerWeb zählt bestimmte Fehlercodes und sperrt eine IP, wenn es in kurzer Zeit zu viele davon gibt. Genau das ist mir bei einem Test passiert, und meine eigene IP war für den Blog gesperrt.
Eine Blockierung durch ModSecurity dagegen taucht nicht in der Bann-Liste auf. Sie steht nur in den Reports, und man muss wissen, wo man sucht. Diesen Unterschied hatte ich lange nicht verstanden. Heute prüfe ich nach jedem Test beide Stellen und halte einen Weg bereit, die eigene Sperre wieder aufzuheben.
Es hat sich gezeigt, dass es viel leichter ist, in einem SIEM zu analysieren, als in allen Logs, die Bunkerweb selbst anlegt.
Syslog-ng: Zwei Pfade statt einem Filter
Mein erster Filter war einfach. Er verwarf alles, was wie Rauschen aussah, und ließ den Rest durch. Das Problem zeigte sich erst, als eine Warnung mit einem Monitoring-Muster im Text verschwand, obwohl sie eine echte Sicherheitsmeldung war. Der Filter hat die Reihenfolge falsch gesetzt.
Die Lösung besteht aus zwei Pfaden. Pfad A sendet ModSecurity- und BADBEHAVIOR-Meldungen immer an Wazuh, unabhängig davon, zu welchem Host sie gehören. Pfad B filtert alle übrigen Meldungen. Die Konfiguration sieht vereinfacht so aus, mit Beispieladressen aus dem Dokumentationsnetz:
@version: 4.8
@include "scl.conf"
options {
keep-hostname(yes);
chain_hostnames(no);
flush_lines(0);
};
source s_bunkerweb {
udp(ip(0.0.0.0) port(514));
tcp(ip(0.0.0.0) port(514));
};
# Pfad A: Sicherheitsereignisse immer weiterleiten
filter f_always_keep {
match('ModSecurity' value("MESSAGE"))
or match('BADBEHAVIOR' value("MESSAGE"))
};
# Pfad B: Rauschen verwerfen
filter f_drop_noise {
match('Uptime-Kuma' value("MESSAGE"))
or match('"OPTIONS ' value("MESSAGE"))
or match('"GET /(health|ping)' value("MESSAGE"))
or match('\[(notice|info|debug)\]' value("MESSAGE"))
};
filter f_security {
match('\[(BUNKERNET|BLACKLIST|GREYLIST|ANTIBOT|LIMIT)\]' value("MESSAGE"))
or match('(banned|blocked|Access denied)' value("MESSAGE"))
or match('"[A-Z]+ [^"]* HTTP/[0-9.]+" [45][0-9][0-9] ' value("MESSAGE"))
};
filter f_grafana {
match('(my-blog-domain|www.my-blog-domain)' value("MESSAGE"))
and match('"(GET|POST) .* HTTP/[0-9.]+" (20[0-9]|30[0-9]) ' value("MESSAGE"))
and not match('\.(css|js|png|jpe?g|svg|woff2?)' value("MESSAGE"))
};
destination d_wazuh {
udp("192.0.2.20" port(514));
};
log {
source(s_bunkerweb);
filter(f_always_keep);
destination(d_wazuh);
flags(final);
};
log {
source(s_bunkerweb);
filter { not filter(f_drop_noise); };
filter { filter(f_security) or filter(f_grafana); };
destination(d_wazuh);
};
Der Wert flags(final) im ersten Pfad sorgt dafür, dass Sicherheitsmeldungen nicht doppelt ankommen. Die Beispieladresse 192.0.2.20 gehört zum Dokumentationsnetz und ist kein echter Server.
Ein Stolperstein blieb: Die Blockierungen stehen in den Error-Logs, nicht in den Access-Logs. Ohne die Variable für die Error-Logs kam bei Wazuh nur die Hälfte an. Die Ergänzung in der BunkerWeb-Umgebung sieht so aus:
environment:
ACCESS_LOG_1: "syslog:server=syslog-ng:514,tag=bunkerweb,facility=local0"
ERROR_LOG_1: "syslog:server=syslog-ng:514,tag=bunkerweb_error,facility=local0"
Diesen Fehler hätte ich ohne den Vergleich zwischen Wazuh-Alarmen und Container-Logs nicht bemerkt. Wer nur in Wazuh schaut, sieht die fehlenden Zeilen nicht, denn er weiß nicht, dass sie existieren.
Decoder: Warnungen und Blockierungen trennen
Eine Warnung und eine Blockierung enthalten unterschiedliche Informationen. Die Warnung hat eine Regel-ID, eine Meldung und eine unique_id, aber keinen Statuscode. Die Blockierung hat den Statuscode. Ein gemeinsamer Decoder hat beides vermischt, und die Regeln konnten nicht sauber unterscheiden.
Zwei Decoder auf Basis des gemeinsamen Parents lösen das. Der Warn-Decoder sieht so aus:
<decoder name="bunkerweb-modsec-warn">
<parent>bunkerweb</parent>
<prematch type="pcre2">ModSecurity: Warning\.</prematch>
<regex type="pcre2">ModSecurity: Warning\..*?\[id "(\d+)"\].*?\[msg "([^"]*)"\].*?\[hostname "([^"]+)"\].*?\[uri "([^"]*)"\].*?\[unique_id "([^"]+)"\].*?client: ([^,\s]+)</regex>
<order>modsec_id, modsec_msg, vhost, url, unique_id, srcip</order>
</decoder>
Das Feld unique_id ist entscheidend. Damit lassen sich alle Warnungen einer einzigen Anfrage zusammenfassen, und die Statistik zählt Anfragen statt Regeltreffer.
Bei der Blockierung habe ich die Feldextraktion nicht vollständig geschafft. Der Decoder erkennt die Zeile, liefert aber nicht alle Felder zuverlässig. Deshalb habe ich die Regel für die Blockierung so formuliert, dass sie das Muster direkt im vollständigen Log findet:
<rule id="100421" level="12">
<if_sid>100400</if_sid>
<match>ModSecurity: Access denied with code</match>
<description>BunkerWeb ModSecurity: Angriff wurde mit HTTP 403 blockiert (Details im full_log)</description>
<mitre><id>T1190</id></mitre>
<group>web,attack,active_response_trigger,pci_dss_10.2.4,</group>
</rule>
Die Warnung bekommt dagegen Stufe 8:
<rule id="100420" level="8">
<decoded_as>bunkerweb-modsec-warn</decoded_as>
<description>BunkerWeb ModSecurity: $(modsec_msg) (Rule: $(modsec_id)) on $(vhost)</description>
<group>web,attack,pci_dss_10.2.4,gdpr_IV_35.7.d,tsc_CC6.1,</group>
</rule>
Testen kann man das mit wazuh-logtest, aber nur richtig. Die Testzeile braucht den Syslog-Kopf mit Zeitstempel, Host und Programmname, sonst findet kein Decoder den Parent. Ich habe diesen Fehler mehrfach gemacht und dabei gedacht, der Decoder sei kaputt. Er war in Ordnung, nur meine Testzeile nicht. Nach jeder Änderung an Decodern oder Regeln braucht Wazuh außerdem einen Neustart, sonst lädt der laufende Prozess die neue Datei nicht.
Was die Scanner suchen
Die Logs zeigen ein Muster, das ich vorher unterschätzt habe. Ein Teil der Anfragen stammt von Scannern aus Cloud-Netzen, die gezielt nach Dateien fragen, die auf keinem gepflegten Server liegen sollten. Dazu gehören Umgebungsdateien mit Zugangsdaten, Versionsverwaltungsverzeichnisse und Konfigurationsdateien von Cloud-Diensten.
Die WAF hat die Anfragen zuverlässig abgewiesen. Trotzdem war die Frage wichtig, ob solche Dateien überhaupt auf dem Host liegen. Ich habe die Verzeichnisse der betroffenen Dienste durchsucht und nichts gefunden. Seitdem prüfe ich regelmäßig, ob in öffentlich erreichbaren Verzeichnissen etwas liegt, das dort nichts zu suchen hat. Eine WAF ist eine Verteidigungslinie, aber das Dateisystem sollte nicht darauf angewiesen sein.
Was sich dadurch verbessert hat
Die Sicherheit hat sich auf drei Ebenen verbessert.
Erstens sehe ich, was passiert. Früher wusste ich nicht, ob ein Problem eine Blockierung, eine Warnung oder ein Konfigurationsfehler war. Heute steht jede Warnung mit unique_id in Wazuh, und jede Blockierung löst einen Alarm der Stufe 12 aus, die von meiner KI untersucht wird.
Zweitens sind die Ausnahmen nachvollziehbar. Jede Ausnahme steht in einer eigenen Custom Configuration mit einem Kommentar, warum sie existiert und welche Felder sie betrifft. Wenn ich in einem halben Jahr vergessen habe, warum eine Regel für einen Pfad abgeschaltet ist, steht der Grund daneben.
# =============================================================================
# ModSecurity-Ausnahmeregeln
# Beispielkonfiguration für eine Webanwendung mit OWASP CRS
# Diese Datei wird vor den CRS-Regeln geladen.
# =============================================================================
# -----------------------------------------------------------------------------
# 1) Ausnahme für einen authentifizierungspflichtigen API-Endpunkt
# Bestimmte CRS-Regeln werden für einen definierten API-Bereich deaktiviert,
# da es bei legitimen Anfragen zu Fehlalarmen kam.
# Hinweis: Ausnahmen möglichst eng begrenzen und regelmäßig überprüfen.
# -----------------------------------------------------------------------------
SecRule REQUEST_URI "@beginsWith /<REDACTED_API_PATH>/" \
"id:<CUSTOM_RULE_ID_1>,\
phase:1,\
pass,\
nolog,\
ctl:ruleRemoveById=<CRS_RULE_ID_1>,\
ctl:ruleRemoveById=<CRS_RULE_ID_2>,\
ctl:ruleRemoveById=<CRS_RULE_ID_3>,\
ctl:ruleRemoveById=<CRS_RULE_ID_4>,\
ctl:ruleRemoveById=<CRS_RULE_ID_5>"
# -----------------------------------------------------------------------------
# 2) Ausnahme für einen HTTP-Header
# Ein bestimmtes Header-Feld wird bei ausgewählten HTTP-Methoden von einer
# CRS-Regel ausgenommen, um legitime Anfragen zu ermöglichen.
# -----------------------------------------------------------------------------
SecRule REQUEST_METHOD "@rx ^(GET|HEAD)$" \
"id:<CUSTOM_RULE_ID_2>,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=<CRS_RULE_ID_HEADER>;REQUEST_HEADERS:<HEADER_NAME>"
....Drittens bleibt der Schutz für alles andere erhalten. Der öffentliche Blog, die übrigen Dienste und die Anmeldungen laufen unverändert unter der vollen Prüfung. Die Ausnahmen wirken nur an den Stellen, an denen ich sie begründen kann.
Analyse im SIEM
Da ich nun im SIEM die bereinigten Informationen habe, kann ich dort aus den Daten passende Dashboards und Visualisierungen erstallen.

So sehe ich z.B. in meinem Grafana-Dashboard, von welchen Orten mein Blog gelesen wird:

Genauso kann ich sehen, von wo aus mein Blog angegriffen wird:

Für umfangreichere KI-gestützte Vorfallsanalysen lässt sich dieser Ansatz mit dem bereits beschriebenen Aufbau aus Wazuh on Steroids verbinden. Das Modell wird dort nicht laufend mit jedem Ereignis beschäftigt, sondern bei Bedarf für die vertiefte Analyse eingesetzt. So entsteht bei entsprechendem Anlass ein angereicherter Alarm:
Incident Report: Wiederholte Zugriffsversuche durch bösartige IP (SemrushBot)
Erfassung und Analyse von HTTP-Anfragen von einer als bösartig eingestuften IP-Adresse an den DMZ-Webserver.
Management Summary
Zwischen dem 06.10.2026 um 13:59:48 CEST und 16:50:57 CEST wurden mehrere HTTP-GET-Anfragen von der öffentlichen IP 198.51.100.23 an den Webserver blog.example.invalid (interne IP: 192.0.2.10) registriert. Der User-Agent identifizierte sich als SemrushBot. Die IP-Adresse weist eine sehr schlechte Reputation auf (Missbrauchs-Score 100/100, gelistet auf mehreren Blacklists). Alle Anfragen wurden vom Webserver mit dem Statuscode 403 Forbidden abgewiesen. Es kam zu keiner Kompromittierung des Systems.
Beteiligte Komponenten
| Komponente | IP-Adresse / Hostname | Funktion im Incident |
|---|---|---|
| Firewall (DMZ-Gateway) | 192.0.2.1 |
Protokolliert den eingehenden Traffic (filterlog) und leitet Pakete an die DMZ weiter. |
| Webserver (WAF / Reverse Proxy) | 192.0.2.10 |
Empfängt die HTTP-Anfragen, wendet WAF-Regeln an und antwortet mit 403. |
| SIEM | siem01 |
Bewertet die Quell-IP anhand von Logs und Reputation und löst Active Response aus. |
| Angreiferquelle | 198.51.100.23 |
Quelle der HTTP-Anfragen (SemrushBot). |
Täterprofil: 198.51.100.23
Detaillierte Reputation und Quellen
- Missbrauchsdatenbank: über 1.000 Meldungen in den letzten 90 Tagen. Nutzungstyp: Data Center / Web Hosting.
- VirusTotal: Letzte Analyse am 2026-10-05. 10 Engines melden Auffälligkeiten.
- Spam-Datenbank: als Spam-Quelle auf mehreren Websites gemeldet.
- WHOIS: Netzblock eines Hosting-Anbieters, Registrierung in Zypern.
- Shodan: keine verwertbaren Daten verfügbar.
Erfolgsanalyse
Die Firewall hat die Pakete passieren lassen, da sie an einen Webserver in der DMZ gerichtet waren. Die eigentliche Abwehr erfolgte auf Anwendungsebene durch die WAF.
FAZIT: Angriff erfolgreich abgewehrt
Begründung:
- Alle HTTP-Requests endeten mit dem Statuscode 403 Forbidden. Der Webserver hat die Anfragen also verweigert, bevor sie die Anwendung erreichten.
- Es wurden keine Statuscodes wie
200 OKoder302 Redirectbeobachtet, die auf einen erfolgreichen Zugriff hindeuten. - Das SIEM hat die Aktivität erkannt und einen Alarm ausgelöst.
- Da es sich um GET-Requests auf öffentliche Pfade handelt, die blockiert wurden, ist kein Datenabfluss oder eine Codeausführung wahrscheinlich.
Angriffs- und Payload-Analyse
Der Angreifer nutzte einen automatisierten Bot, um verschiedene Pfade der Website abzurufen. Es handelt sich nicht um einen klassischen Exploit-Versuch wie SQL-Injection oder XSS, sondern um Crawling bzw. Scanning. Die Blockierung durch die WAF erfolgte aufgrund der Reputation der IP-Adresse.
Beobachtete Request-Pfade
/author/beispiel//beispiel-artikel-eins//author/max//beispiel-artikel-zwei/
User-Agent-String
Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)
Analyse: SemrushBot ist ein bekanntes SEO-Tool. Während Semrush-Bots legitim sein können, weist diese IP-Adresse eine sehr hohe Missbrauchshistorie auf. Es ist wahrscheinlich, dass die Adresse missbraucht wird, um als „weißer“ Bot aufzutreten, während sie tatsächlich für aggressives Scraping, Spam oder Reconnaissance genutzt wird. Die Blockierung anhand der IP-Reputation ist daher korrekt.
Chronologischer Ablauf (Timeline)
| Zeitstempel (CEST) | Komponente | Ereignis | Kurzbeschreibung |
|---|---|---|---|
| 06.10.2026 13:59:48 | Firewall / Webserver | HTTP GET /author/beispiel/ | Paket von 198.51.100.23 an 192.0.2.10:443. Antwort: 403 Forbidden. |
| 06.10.2026 13:59:50 | SIEM (ntfy) | Alarm ausgelöst | Benachrichtigung über Zugriff durch bekannte bösartige IP. |
| 06.10.2026 15:42:19 | Firewall / Webserver | HTTP GET /beispiel-artikel-eins/ | Weiterer Versuch. Antwort: 403 Forbidden. |
| 06.10.2026 15:42:22 | SIEM (ntfy) | Alarm ausgelöst | Wiederholte Benachrichtigung. |
| 06.10.2026 15:46:31 | Firewall / Webserver | HTTP GET /author/max/ | Weiterer Versuch. Antwort: 403 Forbidden. |
| 06.10.2026 16:50:57 | Firewall / Webserver | HTTP GET /beispiel-artikel-zwei/ | Letzter beobachteter Versuch im Logfenster. Antwort: 403 Forbidden. |
| 06.10.2026 16:50:59 | SIEM (ntfy) | Alarm ausgelöst | Finale Benachrichtigung für diesen Incident-Cluster. |
Beweismittel (Raw Logs, anonymisiert)
Die relevantesten Logauszüge als Beweismittel:
# Firewall-Log (Eingang)
Oct 6 16:50:57 fw01 filterlog[861]: ... vtnet1,match,pass,in,... tcp,60,198.51.100.23,192.0.2.10,63904,443 ...
# Webserver-Log (WAF)
Oct 6 16:50:57 webserver01 bunkerweb: blog.example.invalid 198.51.100.23 - ... "GET /beispiel-artikel-zwei/ HTTP/1.1" 403 69301 "-" "Mozilla/5.0 (compatible; SemrushBot/7~bl; +http://www.semrush.com/bot.html)"
# SIEM-Alarm (Wazuh)
{"timestamp":"2026-10-06T16:50:57.385+0200","rule":{"level":12,"description":"BunkerWeb: Access from known malicious IP (198.51.100.23)","id":"100440"},"data":{"srcip":"198.51.100.23","url":"/beispiel-artikel-zwei/","http_status_code":"403"}}
Risikobewertung und Handlungsempfehlung
Begründung der Kritikalität
Obwohl die IP-Adresse eine sehr hohe Missbrauchshistorie aufweist, war der konkrete Angriff erfolglos. Die Anfragen wurden blockiert, und es gab keine Anzeichen für Datenlecks oder Systemmanipulation. Das Risiko liegt vor allem in einer späteren Nutzung dieser IP für aggressivere Angriffe oder Spam-Kampagnen.
Konkrete nächste Schritte
- IP-Sperre prüfen: Stelle sicher, dass die Adresse dauerhaft an der Firewall oder in der WAF-Blockliste eingetragen ist.
- Logs sichern: Archiviere die Logs dieses Incidents für eine mögliche forensische Auswertung.
- Monitoring anpassen: Prüfe, ob die Alarmschwelle für bekannte bösartige IPs angepasst werden sollte, um Alert-Fatigue zu vermeiden.
- Keine weiteren Maßnahmen am Webserver: Da keine Kompromittierung stattfand, sind keine Bereinigungsmaßnahmen oder Passwort-Resets nötig.
Was ich noch offen lasse
Nicht alles ist fertig, und ich möchte das nicht schönreden.
Die Feldextraktion für Blockierungen fehlt noch teilweise. Die Statistik nach Quell-IP und Ziel funktioniert deshalb für Warnungen, für Blockierungen nur über die Zählung. Die Regel für BADBEHAVIOR-Sperren habe ich noch nicht sauber gegen echte Logzeilen geprüft. Und jedes Update von BunkerWeb oder des Core Rule Sets kann Ausnahmen verändern oder neue False Positives erzeugen. Deshalb gehört eine Kontrolle der Logs nach jedem Update fest in meinen Ablauf.
Fazit
Die WAF schützt meine Dienste heute besser als am Anfang und stört mich deutlich weniger im täglichen Betrieb. Es erleichtert mich, dass die Angriffe nun direkt an der Haustür geklärt werden und nicht erst im Haus/auf dem Server selbst. Den Weg dorthin bin ich in kleinen Schritten gegangen: beobachten, eine Ausnahme eng fassen, testen, dokumentieren. Ja. Viele Fehler waren meine eigenen, etwa falsch formulierte Testzeilen oder ein Filter in der falschen Reihenfolge. Am Ende steht aber jetzt ein System, bei dem ich weiß, was es tut, und das mir zeigt, wenn jemand anklopft.