Stats — Anti-Bot-Leitstelle für phpBB

Seit dem Boom des LLM-Trainings wird das Forum von nicht-menschlichem Traffic überrollt: Scraper, die von Mitgliedern verfasste Inhalte absaugen, Bots, die Endpunkte nach Schwachstellen abklopfen, verteilte IPs, die Anhänge parallel herunterladen, um Rate-Limits zu umgehen. Stats ist die Erweiterung, die ich entwickelt habe, um diesen Traffic zu beobachten, einzustufen und automatisch über fail2ban zu sperren. Hier ist, was sie konkret tut.

Der Kontext: warum diese Erweiterung existiert

Ein nacktes phpBB hat keine eingebaute Möglichkeit zu wissen, wer es wirklich besucht. Apache-Logs zeigen Aufrufe, aber keine Absicht. Ein legitimes Chrome 124 und ein Scraper, der sich als Chrome 124 ausgibt, sind allein anhand des User-Agent nicht zu unterscheiden. Der Unterschied ist jedoch enorm: der eine liest ein paar Seiten, der andere leert das gesamte Forum in Stunden — oft verteilt auf Hunderte von IPv6-Adressen aus einem einzigen /48, um IP-basierte Rate-Limits zu unterlaufen.

Die Erweiterung sitzt zwischen Apache und fail2ban. Sie beobachtet jeden Besuch auf der PHP-Seite — dort, wo die echten Signale erreichbar sind (Cookies, JS, Verhaltens­telemetrie) — und gibt strukturierte Events in ein dediziertes Logfile aus. Dieses Log wird dann von fail2ban-Filtern gelesen, die über Sperren entscheiden. Die Erweiterung sperrt nie selbst: sie produziert lediglich ein verwertbares Signal.

Was man im ACP sieht

Das ACP ist kein passives Reporting-Tool: es ist die Leitstelle. Jeder Tab beantwortet eine operative Frage.

  • Übersicht — wie viele Besuche heute? Welcher Anteil mutmaßlicher Menschen, Graubereich, bestätigte Bots? Der probabilistische Donut wird von session_probability_model.php berechnet, das die beobachteten Signale zu einer P(bot) pro Sitzung kombiniert. Jede Sitzung zeigt ihr Badge.
  • Sitzungen — die Timeline jedes Besuchers: aufgerufene Seiten, Reihenfolge, Zeit zwischen Klicks, Anhang-Downloads, ausgelöste Signale, Cookie-Zustand, Ergebnis der AJAX-Telemetrie. Hier erkennt man einen Scraper sofort: 200 Seiten in 4 Sekunden, keinerlei Mausbewegung, keine Bildschirmauflösung.
  • Seiten — Top-URLs mit vollständigem Referer. Sehr nützlich, um eine Drittseite zu erkennen, die einen eigenen Crawler einsetzt.
  • Karte — geografische Verteilung (jVectorMap). Landesweite Wellen springen sofort ins Auge, sobald ein neuer Akteur die Jagd eröffnet.
  • Verhalten — der Tab, der alles ändert. Statistische Profile, gelernt aus angemeldeten Mitgliedern (menschliche Referenz), Vergleich mit Gästen, Erkennung von Outliern, SVG-Spuren des Cursors, wenn sie erfasst werden konnten, und Historie der Grenzfälle.

Wie sie erkennt: fünf Schichten

Die Erweiterung stapelt fünf Signalfamilien. Keine reicht für sich allein — erst ihre Kombination liefert robuste Entscheidungen.

1. HTTP/UA-Signale — sofort, serverseitig

Werden ab dem ersten Request ausgelöst, ohne dass JS abgewartet wird. Sie erreichen fail2ban am schnellsten. Beispiele: empty_ua (gar kein User-Agent), fake_chrome_build (Chrome-UA mit inkonsistenter Build-Nummer), fake_legit_bot (gibt sich als Googlebot aus, aber das Reverse-DNS widerlegt das), html_entities_in_url (URL mit &amp%3B — ein Scraper, der aus dem HTML-Quelltext kopierte Links abspielt), posting_first_visit (POST auf posting.php beim allerersten Aufruf — kein Mensch macht das).

2. JS-Signale — AJAX-Telemetrie

Der Browser führt ein Skript aus, das misst: Bildschirmauflösung, Fenstergröße, Vorhandensein der Eigenschaft navigator.webdriver, Scroll-Profil (Geschwindigkeit, Plateaus, Sprünge), Maus- oder Touch-Events. Alles wird an den abgesicherten Endpunkt POST /stats/px gesendet (sitzungs­gebundenes Link-Token + Same-Origin-Prüfung). Ein Headless-Bot löst dieses Skript nie aus oder hinterlässt unsinnige Spuren: ajax_webdriver, ajax_scroll_too_fast, cursor_no_movement, no_screen_res.

3. Gelernte statistische Signale — die menschliche Baseline

Statt fester Schwellen lernt die Erweiterung aus der echten Aktivität angemeldeter Mitglieder (per Definition Menschen). Verteilung der Lesegeschwindigkeit, Scroll-Dichte, Sprung-Quote, Interaktionsfrequenz — alles wird gemessen, und die learn_*_outlier-Signale schlagen an, wenn ein Besucher zu weit von dieser Verteilung abweicht. Vorteil: die Baseline rekalibriert sich pro Forum von selbst.

4. Identitätsgeteilte Signale — geklonte Cookies und Fingerprints

Jeder Besucher erhält ein signiertes Sitzungs-Cookie, gehashed in der Datenbank gespeichert. Solange es gültig ist, dient es als primärer Sitzungsanker — auch bei IP-Wechseln (IPv6 Privacy Extensions, Mobile NAT). Wird das gleiche Cookie oder der gleiche Browser-Fingerprint von mehreren verschiedenen IPs vorgelegt, gibt die Erweiterung guest_cookie_clone_multi_ip oder guest_fp_clone_multi_ip aus — nahezu sicherer Hinweis auf ein Botnet, das gestohlene Identitäten teilt.

5. Verzögerte Signale — der geo_async-Cron

Manche Erkennungen ergeben erst im Nachhinein Sinn. Der geo_async-Cron löst IPs über ip-api.com auf (DB-Cache pro IP und pro /24-Präfix, um API-Aufrufe zu sparen) und macht dann zweierlei: (a) er markiert Gäste aus Zielländern, die nur eine Seite ohne Interaktion innerhalb von 5 Minuten gesehen haben (cn_no_interaction_5m); (b) er erkennt verteilte Downloads: zu viele Anhänge, von zu vielen benachbarten IPs in einem kurzen Fenster → xip_dl_soft_v1 oder xip_dl_hard_v1 je nach Score.

Die fail2ban-Brücke: wo Beobachtung zu Handlung wird

Die Erweiterung schreibt zwei Zeilentypen in /var/log/security_audit.log (Pfad im ACP konfigurierbar):

2025-11-01 14:32:17 PHPBB-SIGNAL ip=1.2.3.4 session=abc123 user_id=0 signals="fake_legit_bot,no_screen_res" ua="Mozilla/5.0 ..." page="/viewtopic.php?t=42" cc=CN
2025-11-01 14:33:05 PHPBB-XIP ip=1.2.3.4 cc=DE method=xip_dl_soft_v1 severity=soft score=72 topic_id=18 downloads=9 views=0 period_sec=3600

PHPBB-SIGNAL wird live während des Besuchs ausgegeben. PHPBB-XIP wird nachträglich vom Cron emittiert. Beide tragen IP, Ländercode und Signal-Liste — alles, was fail2ban braucht.

2026-05-20 18:22:48 PHPBB-SIGNAL ip=123.21.186.XXX session=2fdcda1c…XXX user_id=0 signals="old_chrome_106" ua="Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/106.0.5249.119" page="/download/file.php?id=2817&sid=…" cc=VN
2026-05-20 18:25:59 PHPBB-SIGNAL ip=177.128.53.XXX session=b584ea20…XXX user_id=0 signals="posting_first_visit" ua="Opera/8.13.(Windows NT 4.0; mni-IN) Presto/2.9.177" page="/posting.php?mode=quote&p=201159" cc=BR
2026-05-20 18:28:21 PHPBB-SIGNAL ip=180.191.236.XXX session=346bd8f9…XXX user_id=0 signals="old_chrome_42,fake_chrome_build" ua="Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_10_0) Chrome/42.0.887.0" page="/download/file.php?id=90985" cc=PH
2026-05-20 18:32:11 PHPBB-XIP ip=192.99.7.XXX cc=CA method=xip_dl_soft_v1 severity=soft score=68 topic_id=24 downloads=11 views=0 period_sec=3600

Mitgelieferte Filter und Jails

Im fail2ban/-Ordner der Erweiterung liegen 13 einsatzbereite Filter. Auf diesem Server sind sie nach /etc/fail2ban/filter.d/ verlinkt und werden von ebenso vielen Jails gelesen. Die wichtigsten:

  • phpbb-badbot-confirmed — Hochsignifikante Signale (fake_legit_bot, posting_first_visit, fake_chrome_build, empty_ua, etc.): 1 Treffer genügt, 24 h Bann, ×2-Eskalation bis 30 Tage.
  • phpbb-badbot-suspicious — moderate Signale (no_screen_res, ajax_scroll_*, learn_*_outlier): 3 Treffer, 12 h Bann, mit Eskalation.
  • phpbb-guest-cookie-clone / phpbb-guest-fingerprint-clone — Cookie oder Fingerprint von mehreren IPs geteilt: 1 Treffer, progressiver Bann bis 14 Tage.
  • phpbb-cn-no-interaction — Gast aus einem Zielland, der nicht bleibt: bannt das ganze /24 für 3 Tage (eigene nftables-Action).
  • phpbb-crossip-soft / phpbb-crossip-hard — verteiltes Herunterladen, vom Cron erkannt: hard = lange Banns mit inkrementellem Backoff.
  • phpbb-combo-scraper — Aggregator, der sperrt, wenn mehrere verschiedene Signale in einem kurzen Fenster auf dieselbe IP konvergieren.

Alle Jails verwenden bantime.increment = true: ein Wiederholungstäter sieht seinen Bann bei jedem Rückfall verdoppelt, bis zu mehreren Wochen. Kurze Sperren sind die Regel, lange Sperren werden verdient.

Wie es in der Produktion auf diesem Server aussieht

Einige Produktionszahlen von forum.debucquoi.com zum Zeitpunkt dieses Beitrags:

  • 22.049 IPs insgesamt gesperrt durch phpbb-badbot-confirmed — davon 5.191 aktuell aktiv.
  • 462 /24-Subnetze gesperrt durch phpbb-cn-no-interaction (366 aktuell aktiv).
  • 44.433 PHPBB-SIGNAL-Events im Log über wenige Monate.
  • 14.132 Treffer auf das Signal posting_first_visit — so viele Bots, die einen direkten POST auf /posting.php versucht haben, ohne zu navigieren.
  • ~10.000 kumulierte Treffer auf die Familie old_chrome_* — UAs, gefälscht auf älteren Chrome-Versionen mit inkonsistenten Build-Nummern.
$ fail2ban-client status phpbb-badbot-confirmed
Status for the jail: phpbb-badbot-confirmed
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     10141
|  `- File list:        /var/log/security_audit.log
`- Actions
   |- Currently banned: 5399
   |- Total banned:     22869
   `- Banned IP list:   45.155.205.XXX 185.220.101.XXX 80.82.78.XXX 91.197.91.XXX …

Warum das ohne False Positives hält

  • Reverse-DNS-Prüfung für legitime Bots: ein Googlebot- oder Bingbot-UA wird gegen das Reverse-DNS abgeglichen; Abweichungen lösen fake_legit_bot aus. Echte Google-/Bing-Crawler kommen durch.
  • Pro Site gelernte menschliche Baseline: die Schwellwerte der learn_*-Signale sind nicht hartcodiert, sie stammen aus der tatsächlichen Verteilung angemeldeter Mitglieder. Ein langsames und ein schnelles Forum bekommen automatisch unterschiedliche Schwellen.
  • Signiertes Cookie als stabiler Anker: eine Sitzung hängt nicht mehr von der IP ab. Ein Mobilgerät, das alle 30 Minuten die IP wechselt, wird nicht für zwei Besucher gehalten, und ein Scraper, der 200 IPs rotiert, kann sich nicht verstecken, wenn er das Cookie behält.
  • Harte und weiche Signale getrennt: erstere lösen sofortige Sperren aus, letztere erfordern mehrere Vorkommen. Kein Mensch wird wegen eines einzigen schwachen Indikators gesperrt.