Quellcode: github.com/Alex0176/odoo-web-rbl — AGPL-3, frei verwendbar.
Wer einen Odoo-Server im Internet betreibt, bekommt Besuch. Nicht gelegentlich, sondern ununterbrochen. Über siebzehn Tage haben wir auf unseren fünf Webseiten 731.297 Anfragen mitgeschrieben und ausgewertet. 110.511 davon waren keine Besucher, sondern automatisierte Suche nach Schwachstellen, Zugangsdaten und Konfigurationsdateien.
Dieser Beitrag beschreibt Web RBL, ein Open-Source-Modul für Odoo 19 Community, das diesen Verkehr erkennt, verbucht und abweist — und das die Sperrliste so veröffentlicht, dass eine Firewall sie unmittelbar verwenden kann. Alle Zahlen stammen aus dem Echtbetrieb.
Was Web RBL schützt — die Schutzwirkung im Einzelnen
Ein Abwehrmodul ist nur so viel wert wie das, was es messbar verhindert. Die Schutzwirkung liegt auf vier Ebenen.
1. Zugangsdaten bleiben unentdeckt
Die größte gemessene Angriffsklasse sucht nicht nach Lücken in Odoo, sondern nach liegengebliebenen Geheimnissen. Über siebzehn Tage:
- 57.721 Anfragen nach
.env-Dateien — dort stehen Datenbankkennwörter, API-Schlüssel und Sitzungsgeheimnisse - 5.535 Anfragen nach Konfigurationsdateien
- 4.388 Anfragen nach Cloud-Zugangsdaten:
credentials.json,service-account.json,firebase-adminsdk.json,rclone.conf,.git-credentials - 2.811 Anfragen nach
.git-Verzeichnissen — wer eines findet, lädt den vollständigen Quellcode samt Historie herunter - 1.907 Anfragen nach Bauanweisungen wie
.github/workflows/deploy.ymloder.gitlab-ci.yml, in denen Registraturen und Schlüsselnamen stehen
Entscheidend ist dabei nicht die Zahl der Anfragen, sondern die Zahl
der verschiedenen Adressen: /credentials.json kam
von 98 Adressen, /.git-credentials von 92. Das ist kein
einzelner Scanner, sondern ein Standardrepertoire, das weltweit
gleichzeitig läuft.
2. Anmeldeversuche laufen ins Leere — dauerhaft
Odoo bringt eine eigene Bremse gegen das Durchprobieren von Kennwörtern mit. Sie hat drei Grenzen, die im Odoo-Quelltext selbst dokumentiert sind: Der Zähler liegt im Arbeitsspeicher des Arbeitsprozesses und wird bei jedem Neustart zurückgesetzt, er verzögert nur statt zu sperren, und er zählt ausschließlich Versuche, die bis zur Kennwortprüfung durchkommen.
Web RBL setzt an denselben Stellen an, schreibt aber in die Datenbank. Damit gilt:
- Der Stand überlebt jeden Neustart
- Erfasst werden beide Wege: die Anmeldemaske
/web/loginund die Schnittstelle/xmlrpc/2/common - Erfasst werden auch Versuche, die nie bis zur Kennwortprüfung gelangen — etwa Anmeldungen ohne gültiges Sitzungsmerkmal
- Ein gescheiterter Zugriff auf den Datenbankverwalter wird mitgezählt; das ist der schwerwiegendste Anmeldeversuch überhaupt
3. Der Angriff kostet den Server nichts mehr
Eine Sonde auf einen Pfad wie /@fs/../../.env war vor
dem Modul teuer: Odoo sucht die Fehlerseite, rendert sie, stolpert
beim Aufbau der Adresse über die Punkt-Segmente und scheitert
daraufhin auch an der Fehlerseite der Fehlerseite. Zwei
Stapelprotokolle je Sonde. Gemessen wurden 28.401 solcher Fehler
in siebzehn Tagen, dazu 9.467 Folgefehler.
Web RBL greift in ir.http._match, also vor der
Wegfindung. Die Antwort fällt ohne Vorlage, ohne Datenbankzugriff
über die Sperrprüfung hinaus und ohne Protokollzeile.
4. Die Firewall wirft den Angreifer hinaus, bevor er ankommt
Die Sperrliste wird unter einer Adresse veröffentlicht, die HAProxy und OPNsense unmittelbar lesen können. Der Angreifer erreicht den Webserver damit gar nicht mehr — und auch keinen anderen Dienst auf demselben Host. Ein einmal als bösartig erkannter Zugriff schützt so rückwirkend die gesamte Maschine.
Drei Stufen: sperren, zählen, melden
Nicht jede Anfrage ins Leere ist ein Angriff. Web RBL unterscheidet deshalb drei Stufen, und die Stufe lässt sich je Muster umstellen, ohne den Code anzufassen.
| Stufe | Die auslösende Anfrage | Folge für die Adresse |
|---|---|---|
| sperren | wird abgewiesen | 24 Stunden gesperrt, nach drei auffälligen Tagen dauerhaft |
| zählen | läuft durch | nur verbucht, keine Sperre |
| melden | läuft durch | verbucht mit Befundtext, wird nie gesperrt |
Zusätzlich lässt sich je Muster eine Schwelle setzen: so viele Treffer desselben Musters, bevor gesperrt wird. Für Anmeldungen steht sie auf zehn, und der Grund ist eine Messung: Von neun gescheiterten Anmeldungen in siebzehn Tagen stammte die Mehrzahl von Kunden, die sich den Zugang selbst auf einem weiteren Gerät einrichteten und dabei die falsche Domain eintrugen — in einem Fall die eigene, nur mit Bindestrich statt Punkt.
Eine Sperre beim ersten Fehlversuch hätte damit nicht Angreifer getroffen, sondern Kunden bei der Einrichtung — und zwar genau dann, wenn sie ohnehin schon Mühe haben. Das ist der praktischste Grund für eine Schwelle, den wir kennen: Die ersten Fehlversuche einer Adresse sagen wenig darüber aus, wer dahintersteht. Erst die Beharrlichkeit tut es.
Wer dagegen weiterklopft, obwohl der Server bereits „bitte kurz warten" geantwortet hat, wird sofort gesperrt. Diese Meldung liest ein Mensch und wartet; wer sie ignoriert, liest sie nicht, weil ihn niemand liest. Ein eigenes Muster mit Schwelle null fängt genau diesen Fall ab, und es kann keinen Kollegen treffen: Dorthin gelangt nur, wer bereits zehn Fehlversuche hinter sich hat und danach weitermacht.
Welche Angriffe erkannt werden
Alle Muster sind gegen echten Verkehr geprüft. Die Zahl der verschiedenen Adressen steht bewusst daneben — sie zeigt, ob ein Muster einen einzelnen Scanner trifft oder ein verbreitetes Vorgehen.
| Angriffsklasse | Anfragen | Adressen |
|---|---|---|
.env-Abgriff | 57.721 | 223 |
| PHP-Sonden | 12.237 | 256 |
| Pfad-Traversal mit lohnendem Ziel | 10.046 | 64 |
| Konfigurationsdateien | 5.535 | 114 |
| Cloud-Zugangsschlüssel | 4.388 | 152 |
| WordPress-Sonden | 3.366 | 385 |
.git-Abgriff | 2.811 | 255 |
| Odoo-Erkundung (Datenbankverwalter, Versionsabfrage) | 2.500 | 213 |
KI-Werkzeugketten (/mcp, /api/fs/exec) | 2.450 | 74 |
| Bauanweisungen (CI/CD) | 1.907 | 88 |
| GraphQL-Endpunkte | 1.878 | 71 |
Bemerkenswert ist die jüngste Klasse. /api/fs/exec
wurde 369 mal von 54 verschiedenen Adressen angefragt — das ist
kein Lesezugriff, sondern der Versuch, Befehle auszuführen. Solche
Ziele gab es vor zwei Jahren nicht; sie stammen aus Schnittstellen
neuer KI-Werkzeuge und Aufgabenwarteschlangen. Wer seine Abwehr nur
auf WordPress und PHP ausrichtet, sieht davon nichts.
Fehlkonfigurationen melden statt sperren
Der häufigste Fall im Echtverkehr ist weder Angriff noch Besucher, sondern defekte Software, die den Webserver für etwas anderes hält:
- 1.367 Aufrufe der Web-Schnittstelle eines QNAP-NAS — ein Sync-Client, dessen Ziel nicht mehr stimmt. Ein einziger Anschluss klopfte 962 mal an.
- 65 Autodiscover-Abfragen — ein Outlook, das die Webdomain für seinen Exchange hält
Solche Adressen werden nie gesperrt. Eine Sperre behebt den Defekt nicht, sie verbirgt ihn — und trifft denjenigen, dessen Gerät ohnehin schon falsch eingestellt ist. Stattdessen entsteht ein Eintrag mit Befundtext und den betroffenen Domains, aus dem auf Wunsch automatisch ein Ticket wird: je Adresse genau eines, und wird es geschlossen und die Adresse fällt erneut auf, öffnet sich dasselbe Ticket wieder, statt dass ein zweites entsteht.
Erkannt werden QNAP, Synology, Exchange-Autodiscover, ActiveSync
sowie WebDAV-, CalDAV- und CardDAV-Clients. Die Muster nennen dabei
exakte Dateinamen und Verzeichnisse, niemals bloße Endungen:
Im selben Verzeichnis /cgi-bin/ liegen die harmlosen
Sync-Aufrufe direkt neben klassischen Sonden wie
printenv.pl.
Die Freiliste: Adressen, die nie gesperrt werden
Der Preis eines Irrtums ist nicht bei allen Adressen gleich. Bei einem Scanner aus einem Rechenzentrum kostet ein Fehlalarm nichts. Bei der Gegenstelle eines Standorttunnels kostet er den Standort.
Deshalb gibt es eine zweite Liste, die vor der ersten steht: Adressen und Netze, die nie gesperrt werden. Sie schaltet dabei nichts stumm — wer darauf steht, wird weiterhin geprüft und verbucht, nur die Folge entfällt. Das ist der Unterschied zu einer Ausnahme in der Firewall: Die macht blind, diese macht nur geduldig.
Gefüllt wird sie täglich aus der Firewall selbst, damit ein neu eingerichteter Tunnel nicht darauf angewiesen ist, dass jemand an die Sperrliste denkt. Dabei gilt eine Regel, die wichtiger ist als der Abgleich: Eine leere Antwort löscht nie. Antwortet die Quelle nicht, leer oder unverständlich, bleibt die Liste unverändert. Eine Freiliste, die sich bei einer Störung selbst leert, nimmt im schlechtesten denkbaren Moment alle Tunnel mit — die Firewall ist nicht erreichbar, also sind auch ihre Gegenstellen nicht mehr geschützt.
Drei Schranken sorgen dafür, dass eine Freilisten-Adresse die ausgelieferte Sperrliste nicht erreichen kann: Sie wird nicht gesperrt, bestehende Sperren werden beim Aufnehmen sofort gelöst, und der Endpunkt filtert zusätzlich. Die dritte ist Absicht — aus dieser Liste baut eine Firewall unbesehen Regeln.
Pflichtangaben und automatisierte Datensammler
Das Impressum ist keine gewöhnliche Seite. Es muss für Menschen erreichbar bleiben, denn eine Sperre darauf schafft genau den Verstoß, den ein Sammler sucht. Zugleich ist es die Seite, auf der Name und Anschrift stehen — und damit das Ziel maschineller Ernte.
Bei der österreichischen Abmahnwelle wegen eingebundener Google-Schriften zeigten die Protokolle, dass die Schreiben nicht aus Einzelbesuchen stammten, sondern aus einem automatisierten Rundumschlag: verschiedene, nicht zusammenhängende Hosts im Abstand von Millisekunden.
Web RBL löst das durch Herabstufung statt Ausnahme. Der
kanonische Pfad /impressum trifft auf gar kein Muster und
bleibt immer erreichbar. Fremde Schreibweisen wie
/impressum.php — die es auf einer Odoo-Seite nicht
geben kann — werden gemeldet, und wer die maschinelle Ernte
nicht hinnehmen will, stellt einen Parameter um.
sitemap und robots sind davon ausgenommen
und grundsätzlich nicht sperrbar: Sie enthalten keine
personenbezogene Angabe, und sie zu holen ist die Aufgabe jedes
Suchmaschinen-Crawlers.
Domain und Kennung: was im Zugriffsprotokoll fehlt
Das Zugriffsprotokoll von werkzeug schreibt weder den angesprochenen Host noch die Kennung des Aufrufers mit. Odoo bekommt beide selbstverständlich — ohne den Host könnte es bei mehreren Webseiten gar nicht die richtige auswählen. Nachlesen kann man es hinterher aber nicht.
Web RBL hält beides je Treffer fest. Das beantwortet zwei Fragen, die sich sonst nicht beantworten lassen: welche Webseite eine Fehlkonfiguration anspricht — und damit, welchem Kunden die Domain gehört — und ob eine Adresse eine Seite besucht oder alle.
Wie viel die Kennung ausmacht, zeigte eine Auswertung auf einer
unserer Webseiten: Über siebzig Prozent des gesamten Verkehrs
entfielen dort auf einen einzigen Aufrufer, verteilt über mehr als
hundert Adressen. Ohne die Kennung hätte das wie ein verteilter
Scraper ausgesehen. Es war GPTBot, der Crawler von OpenAI
— er fragte immer wieder nach robots.txt und
verhielt sich vollkommen korrekt. Eine Sperre wäre hier sogar
schädlich gewesen: Sie hätte die Seite unsichtbar und ohne Weg zurück
aus der Indexierung genommen.
Die Sicherheitsaspekte im Einzelnen
1. Die Proxy-Erkennung — die gefährlichste Stelle überhaupt
Läuft Odoo hinter einem Reverse Proxy und ist proxy_mode
falsch gesetzt, sieht das Modul bei jeder Anfrage dieselbe Adresse:
die des Proxys. Eine Sperre würde dann alle Besucher gleichzeitig
aussperren.
Deshalb prüft Web RBL vor jeder Sperre, ob die Adressbestimmung
verlässlich ist. Es verlangt proxy_mode zusammen mit
einer Weiterleitungs-Kopfzeile, verweigert die Arbeit, wenn eine
Weiterleitung ohne proxy_mode ankommt, und verweigert sie
ebenso bei mehr als einem Zwischenproxy — dann wäre die
ermittelte Adresse die eines Proxys und nicht die des Besuchers. Im
Zweifel wird nicht gesperrt.
2. Eigene Netze niemals
Private Adressbereiche, Loopback, Link-Local und CGNAT sind fest ausgenommen. Sie können unter keinen Umständen auf der Liste landen, auch nicht von Hand.
3. Ein eigener Fehler darf die Webseite nicht mitnehmen
Die gesamte Prüfung läuft in einer Absicherung, die jeden Fehler abfängt: ein Datenbankfehler, ein kaputtes Muster, eine fehlende Tabelle. Was hier schiefgeht, wird protokolliert, und die Anfrage läuft unverändert weiter. Ein Schutzmodul, das bei einem eigenen Fehler die Webseite mitnimmt, ist schlimmer als gar keines.
4. Beobachten vor Sperren
Nach der Installation ist das Sperren abgeschaltet. Sonden werden abgewiesen und verbucht, gewöhnliche Anfragen gelisteter Adressen laufen aber durch. So lässt sich einige Tage mitlesen, wer tatsächlich auf der Liste landet, bevor jemand ausgesperrt wird.
5. Enge Muster statt breiter Netze
Ein falsch erkannter Besucher ist teurer als eine übersehene Sonde.
Jedes Muster trägt deshalb seine Stufe, und sie ist je Muster
umstellbar. Eine Dateiendung allein genügt selten: Bei
.php stehen 14.012 echte Sonden gegen eine Handvoll
Altlinks, bei .asp ist das Verhältnis umgekehrt. Solche
Entscheidungen gehören gemessen, nicht geschätzt.
6. Eine Freigabe von Hand bleibt bestehen
Wer eine Adresse freigibt, hatte einen Grund. Ein weiterer Treffer setzt diese Entscheidung nicht stillschweigend außer Kraft.
7. Die Liste ist geschützt, aber kein Geheimnis
Der Endpunkt verlangt ein Token und antwortet ohne gültiges Token
mit 404, nicht mit 403 — die Existenz
der Adresse wird damit gar nicht erst bestätigt. Der Vergleich läuft
zeitkonstant. Die Liste selbst enthält nichts Vertrauliches: Sie
besteht aus Adressen, die von sich aus bei uns angeklopft haben.
8. Keine Geheimnisse in der Buchhaltung
Bei gescheiterten Anmeldungen wird der versuchte Benutzername vermerkt, niemals das Kennwort. Beim Datenbankverwalter wird aus den übergebenen Werten gar nichts gelesen: Dort steht an vorderster Stelle das Hauptkennwort, und ein Eintrag in einer Sperrliste wandert in jede Datenbanksicherung.
9. Der Köder verrät nichts Verwertbares
Optional beantwortet Web RBL Sonden mit erfundenem Inhalt, der einen eindeutigen Kanarienwert enthält. Wer diesen Wert später abruft, hat die gefälschte Antwort gelesen und daraufhin gehandelt — ein Fehlalarm ist dabei ausgeschlossen. Die Köder enthalten keine verwertbaren Angaben, keine echten Pfade und keine gültigen Zugangsdaten. Ausgeliefert wird diese Funktion abgeschaltet, denn sie ändert das Verhalten nach außen sichtbar.
Einbindung in Firewall und Reverse Proxy
Die Sperrliste steht unter einer eigenen Adresse zur Verfügung, wahlweise als einfache Liste oder als JSON mit Zustand, Trefferzahl und Frist. Eine zweite Liste führt ausschließlich die Adressen der schärfsten Stufe.
Damit lässt sich die Abwehr an die Kante ziehen: HAProxy weist die Anfrage ab, bevor sie Odoo erreicht, und OPNsense verwirft die Pakete, bevor sie den Webserver erreichen. Ein Angreifer, der einmal erkannt wurde, kommt damit an keinen Dienst des Hosts mehr heran — auch nicht an den Mailserver.
Was das Modul nicht kann
Es ersetzt keine Firewall und keine Web Application Firewall. Es erkennt bekannte Sondierungsmuster in Pfaden und gescheiterte Anmeldungen — nicht Angriffe auf Formulare, nicht Einschleusungsversuche in Parameter, nicht den Missbrauch gültiger Zugangsdaten.
Es sieht außerdem nur, was über HTTP zurückkommt. Was ein Angreifer mit erbeuteten Datenbank- oder Postfachzugängen anstellt, bleibt unsichtbar — und genau deshalb sind die Kanarienwerte Pfade und keine Zugangsdaten.
Und es ersetzt keine Sorgfalt: Ein Modul, das
.env-Sonden abweist, macht eine versehentlich
ausgelieferte .env-Datei nicht ungefährlich.
Verfügbarkeit
Web RBL steht unter der AGPL-3 öffentlich zur Verfügung und läuft bei uns produktiv auf fünf Webseiten:
github.com/Alex0176/odoo-web-rbl
Das Modul setzt Odoo 19 Community voraus und hat keine
Abhängigkeiten außer base und web. Die
Ticketanbindung ist optional und benötigt kein bestimmtes
Ticketsystem; ohne ein solches passiert schlicht nichts.
Fragen, Anmerkungen und Fehlerberichte nehmen wir gerne entgegen — am liebsten als Issue auf GitHub.