Desde que explotó el entrenamiento de los LLM, el foro recibe un tráfico no humano enorme: scrapers que aspiran los contenidos escritos por los miembros, bots que sondean los endpoints buscando fallas, IPs distribuidas que descargan los adjuntos en paralelo para esquivar los rate-limits. Stats es la extensión que desarrollé para observar ese tráfico, clasificarlo y bloquearlo automáticamente con fail2ban. Esto es lo que hace concretamente.
El contexto: por qué existe esta extensión
Un phpBB de base no tiene ninguna forma nativa de saber quién lo visita realmente. Los logs de Apache muestran hits pero no la intención. Un Chrome 124 legítimo y un scraper que se hace pasar por Chrome 124 son indistinguibles solo con el User-Agent. Y sin embargo la diferencia es enorme: el uno consume unas páginas, el otro vacía todo el foro en horas, muchas veces repartiendo las peticiones en cientos de direcciones IPv6 de un mismo /48 para esquivar los límites por IP.
La extensión se sitúa entre Apache y fail2ban. Observa cada visita del lado PHP — donde están las señales reales (cookies, JS, telemetría de comportamiento) — y emite eventos estructurados en un log dedicado. Ese log es luego leído por filtros de fail2ban que deciden los baneos. La extensión nunca banea por sí misma: se limita a producir una señal accionable.

Lo que se ve en el ACP
El ACP no es una herramienta pasiva de reporting: es la torre de control. Cada pestaña responde a una pregunta operativa.
- Vista general — ¿cuántas visitas hoy? ¿Qué proporción de humanos presuntos, casos ambiguos, bots confirmados? El anillo probabilístico lo calcula
session_probability_model.phpque combina las señales observadas en unaP(bot)por sesión. Cada sesión muestra su badge. - Sesiones — la línea de tiempo de cada visitante: páginas vistas, orden, tiempo entre clics, descargas de adjuntos, señales emitidas, estado del cookie, resultado del AJAX de telemetría. Aquí se ve un scraper al instante: 200 páginas en 4 segundos, cero movimiento de ratón, sin resolución de pantalla.
- Páginas — top URLs visitadas con referers completos. Muy útil para detectar un sitio tercero que opera su propio crawler.
- Mapa — distribución geográfica (jVectorMap). Las olas por país saltan a la vista en cuanto un nuevo actor entra en cacería.
- Comportamientos — la pestaña que lo cambia todo. Perfiles estadísticos aprendidos a partir de los miembros conectados (referencia humana), comparación con los invitados, detección de outliers, trazas SVG del cursor cuando se pudieron capturar, e historial de casos marginales.



Cómo detecta: cinco capas
La extensión apila cinco familias de señales. Ninguna es suficiente por sí sola: es su combinación lo que produce decisiones robustas.
1. Señales HTTP/UA — instantáneas, lado servidor
Se emiten desde la primera petición, sin esperar JS. Son las más rápidas hacia fail2ban. Ejemplos: empty_ua (sin User-Agent), fake_chrome_build (UA Chrome con número de build incoherente), fake_legit_bot (dice ser Googlebot pero el reverse-DNS lo desmiente), html_entities_in_url (URL con &%3B — scraper que reproduce enlaces copiados del HTML fuente), posting_first_visit (POST a posting.php en el primer hit — ningún humano hace eso).
2. Señales JS — telemetría AJAX
El navegador ejecuta un script que mide: resolución de pantalla, tamaño de la ventana, presencia de navigator.webdriver, perfil de scroll (velocidad, mesetas, saltos), eventos de ratón o de touch. Todo se envía al endpoint seguro POST /stats/px (token de enlace ligado a la sesión + control same-origin). Un bot headless nunca dispara ese script o deja trazas absurdas: ajax_webdriver, ajax_scroll_too_fast, cursor_no_movement, no_screen_res.
3. Señales estadísticas aprendidas — la línea base humana
En lugar de umbrales rígidos, la extensión aprende de la actividad real de los miembros conectados (humanos por definición). Distribución de velocidades de lectura, densidad de scroll, ratio de saltos, frecuencia de interacción: todo se mide, y las señales learn_*_outlier se disparan cuando un visitante se aleja demasiado de esa distribución. Ventaja: la línea base se autocalibra por foro.
4. Señales de identidad compartida — cookie y fingerprint clonados
Cada visitante recibe un cookie de sesión firmado, almacenado hasheado en la base. Mientras es válido, es el ancla principal de la sesión — incluso si la IP cambia (privacy extensions IPv6, NAT móvil). Si la misma cookie o la misma huella del navegador es presentada desde varias IPs distintas, la extensión emite guest_cookie_clone_multi_ip o guest_fp_clone_multi_ip — casi seguro signo de una botnet que comparte identidades robadas.
5. Señales diferidas — el cron geo_async
Ciertas detecciones solo tienen sentido a posteriori. El cron geo_async resuelve las IPs vía ip-api.com (caché DB por IP y por prefijo /24 para ahorrar llamadas) y luego hace dos cosas: (a) marca a los invitados de un país objetivo que solo vieron una página sin interacción en menos de 5 minutos (cn_no_interaction_5m); (b) detecta descargas distribuidas: demasiados adjuntos vistos desde demasiadas IPs cercanas en una ventana corta → xip_dl_soft_v1 o xip_dl_hard_v1 según el score.

El puente fail2ban: donde la observación se vuelve acción
La extensión escribe dos tipos de líneas en /var/log/security_audit.log (ruta configurable en el ACP):
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 se emite en tiempo real durante la visita. PHPBB-XIP se emite a posteriori por el cron. Ambos llevan la IP, el código de país y la lista de señales — todo lo que fail2ban necesita.
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
Los filtros y jails que vienen con la extensión
En la carpeta fail2ban/ de la extensión hay 13 filtros listos para usar. En este servidor están symlinkeados desde /etc/fail2ban/filter.d/ y leídos por la misma cantidad de jails. Los que cargan el grueso del trabajo:
- phpbb-badbot-confirmed — señales de alta confianza (
fake_legit_bot,posting_first_visit,fake_chrome_build,empty_ua, etc.): 1 hit basta, baneo 24 h, escalada ×2 hasta 30 días. - phpbb-badbot-suspicious — señales moderadas (
no_screen_res,ajax_scroll_*,learn_*_outlier): 3 hits, baneo 12 h, con escalada. - phpbb-guest-cookie-clone / phpbb-guest-fingerprint-clone — cookie o huella compartidos en varias IPs: 1 hit, baneo progresivo hasta 14 días.
- phpbb-cn-no-interaction — invitado de país objetivo que no se queda: banea el /24 entero por 3 días (acción nftables a la medida).
- phpbb-crossip-soft / phpbb-crossip-hard — descarga distribuida detectada por el cron: hard = baneos largos con backoff incremental.
- phpbb-combo-scraper — agregador que banea cuando varias señales distintas convergen en la misma IP en una ventana corta.
Todos los jails comparten bantime.increment = true: un reincidente ve su baneo duplicarse en cada recaída, hasta varias semanas. Los baneos cortos son la norma, los largos son ganados a pulso.
Lo que esto da en producción en este servidor
Algunas cifras de producción de forum.debucquoi.com al momento de escribir estas líneas:
- 22 049 IPs baneadas en total por
phpbb-badbot-confirmed— 5 191 actualmente activas. - 462 subredes /24 baneadas por
phpbb-cn-no-interaction(366 activas). - 44 433 eventos
PHPBB-SIGNALen el log en pocos meses. - 14 132 hits del señal
posting_first_visit— esa cantidad de bots intentaron un POST directo a/posting.phpsin navegar. - ~10 000 hits acumulados en la familia
old_chrome_*— UAs falsificados sobre versiones antiguas de Chrome con builds incoherentes.
$ 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 …
Por qué esto aguanta sin falsos positivos
- Verificación rDNS para los bots legítimos: un UA Googlebot o Bingbot es confrontado con un reverse-DNS; si no coincide con el dominio esperado, es
fake_legit_bot. Los crawlers reales de Google/Bing pasan. - Línea base humana aprendida por sitio: los umbrales de las señales
learn_*no están codificados, vienen de la distribución real de miembros conectados. Un foro lento y uno rápido tendrán umbrales distintos, automáticamente. - Cookie firmado como ancla estable: una sesión ya no depende de la IP. Un móvil que cambia de IP cada 30 min no se cuenta como dos visitantes, y un scraper que rota 200 IPs no puede esconderse si conserva el mismo cookie.
- Separa señales duras y señales blandas: las primeras gatillan un baneo inmediato, las segundas exigen varias ocurrencias. Ningún humano es baneado por un solo indicador débil.