fail2ban en sshd: el modo normal ignora el 44 % de los intentos (prueba con fail2ban-regex)
en un log sintético de 18 líneas (16 de ataque), sshd[mode=normal] cuenta 9 fallos y no cuenta 4 "Connection closed ... [preauth]" ni 3 "Unable to negotiate"; mode=aggressive cuenta las 16. Con maxretry 5 (valor por defecto), ninguna de las 5 IPs (3 a 4 fallos cada una) se banearía.
Contenido
Este artículo no contiene enlaces de afiliado.
fail2ban lee el log de sshd y banea las IP que acumulan fallos. Lo que cuenta como fallo depende del mode del filtro. Con fail2ban-regex se puede comprobar sin banear a nadie ni tocar el firewall.
Condiciones: fail2ban 1.0.2 en Linux, filtro sshd incluido de serie, un log sintético de 18 líneas en formato syslog con IP de documentación (203.0.113.0/24). No es tráfico real, no se probó con journald ni con otras versiones de OpenSSH, y no se probó el baneo en sí. Script completo al final.
Qué cuenta cada modo
El log tiene 16 líneas de ataque (contraseñas erróneas, usuarios inválidos, conexiones cerradas en preauth, fallos de negociación) y 2 accesos legítimos con clave.
| Modo | Líneas contadas como fallo | Líneas de ataque no contadas |
|---|---|---|
normal |
9 | 7 |
aggressive |
16 | 0 |
En modo normal no se cuentan las 4 líneas Connection closed by authenticating user ... [preauth] ni las 3 Unable to negotiate ... no matching key exchange method found. Las dos IP que solo hacían eso (203.0.113.8 y 203.0.113.9) quedan invisibles. En ambos modos los accesos con Accepted publickey no cuentan.
Segunda sorpresa: maxretry
Con el maxretry = 5 que trae jail.conf por defecto, ninguna IP de la prueba se banearía ni en modo aggressive: tienen 3 fallos cada una, y 4 la que más. Un atacante que prueba 3 contraseñas por IP y cambia de IP pasa por debajo. Si se quiere banear antes, hay que bajar maxretry en el jail, con el riesgo de bloquearte a ti mismo si te equivocas de contraseña (usa ignoreip para tu IP).
Cómo comprobarlo en tu servidor
fail2ban-regex /var/log/auth.log 'sshd[mode=normal]'
fail2ban-regex /var/log/auth.log 'sshd[mode=aggressive]'
Compara la línea Lines:: las missed y ignored te dicen qué intentos no se están contando. Para activar el modo en el jail, filter = sshd[mode=aggressive] en jail.local. Si ya revisas tus copias con restic, esta es la misma idea: comprobar que la herramienta hace lo que crees.
Script completo
#!/bin/bash
# fail2ban-regex: how many sshd attack lines the filter catches in normal vs aggressive mode.
# Usage: test-fail2ban-regex.sh WORKDIR. Synthetic log with documentation IPs (203.0.113.0/24); nothing is banned.
set -e
W=$1; rm -rf "$W"; mkdir -p "$W"; L="$W/auth.log"
H=lab; T="Oct 31 10:00"
n=0; line(){ n=$((n+1)); printf '%s:%02d %s sshd[%d]: %s\n' "$T" $((n%60)) "$H" $((1000+n)) "$1" >> "$L"; }
for i in 1 2 3; do line "Failed password for root from 203.0.113.5 port 4$i ssh2"; done
for i in 1 2 3; do line "Failed password for invalid user admin from 203.0.113.6 port 5$i ssh2"; done
for i in 1 2 3; do line "Invalid user test from 203.0.113.7 port 6$i"; done
for i in 1 2 3 4; do line "Connection closed by authenticating user root 203.0.113.8 port 7$i [preauth]"; done
for i in 1 2 3; do line "Unable to negotiate with 203.0.113.9 port 8$i: no matching key exchange method found. Their offer: diffie-hellman-group1-sha1 [preauth]"; done
for i in 1 2; do line "Accepted publickey for sergio from 192.0.2.10 port 9$i ssh2"; done
echo "lineas totales: $(wc -l < "$L")"
for M in normal aggressive; do
echo "== modo $M"
O=$(fail2ban-regex "$L" "sshd[mode=$M]" --print-all-matched 2>&1)
echo "$O" | grep -E "^Lines:"
echo "fallos contados por IP:"
echo "$O" | sed -n '/Matched line(s):/,/Ignored line(s):/p' | grep -oE '203\.0\.113\.[0-9]+' | sort | uniq -c | sed -E 's/^ +//'
done
echo "maxretry por defecto: $(grep -hE '^maxretry' /etc/fail2ban/jail.conf | head -1)"