Baneos persistentes con Fail2Ban 2018

Impactos: 824

Fase 1: Recolección de IP

Lo primero de todo es construirnos nuestra base de datos de direcciones IP, que cumplan al menos 2 requisitos:
1. Nos hayan atacado alguna vez.
2. No sean de España para no perder visitas en el intento de protegernos.
Esta base de datos la usaremos en la Fase 2: Todo esto para qué? Integración con fail2ban.

La fuente son los logs y/o fuentes de terceros que posiblemente también se alimenten de logs; yo voy a usar los que me proporcionan los siguientes programas:

  1. Fail2ban: un sistema de baneo de IP en base al estudio que hace de los logs de los diferentes servicios (ssh, http…) y expresiones regulares ¿No lo conoces? pues lee VPS Ubuntu: Configuración sobre un dominio 2018 donde se securiza un vps con fail2ban.
  2. Logwatch: Una vez al día recibo resumen de actividad en el servidor
  3. Wordfence: conocido plugin WordPress de seguridad

Las fuentes pueden ser también ficheros /etc/hosts.deny que obtengamos de otros servidores, bases de datos de fuentes abiertas, etc…

Un fichero de este tipo de fuente puede ser así:

This email was sent from your website "SOSpedia" by the Wordfence plugin at Friday 2nd of February 2018 at 03:05:08 AM
The Wordfence administrative URL for this site is: https://sospedia.net/wp-admin/admin.php?page=Wordfence
Wordfence has blocked IP address 217.147.169.235.
The reason is: "Exceeded the maximum number of page not found errors per minute for humans.".
User IP: 217.147.169.235
User hostname: 217.147.169.235
User location: Ukraine

De toda la información, en el proyecto que nos ocupa, nos interesan las direcciones IP, de forma rápida podemos expraer las IP de una cadena con una expresión regular como esta:

“((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9] |[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9] |[01]?[0-9][0-9]?))”

Nos vamos a hacer un script al que le digamos una carpeta donde encontrar ficheros de este tipo, en este ejemplo resulta ser la bandeja de entrada de un buzon que recibe este tipo de correos:

#ipeses.sh
egrep -o -R -h -s "((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9] |[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9] |[01]?[0-9][0-9]?))" $1 |sort|uniq > ips.dat

En $1 se espera una carpeta, aunque si le pasamos un fichero también lo procesará, de ahí el parámetro Recursivo. Como véis va a generar un fichero llamado ips.dat.

Un ejemplo de ejecución seria este:

./ipeses.sh /var/vmail/joseblanco.pro/p/o/s/postmaster/Maildir/.Conocimiento.f2b/


Ahora vamos a extraer o quitar las IP procedentes de España, algún falso positivo por consumo excesivo, quizá un script kiddie, para ello usaremos Python, y las librerias Geoip.

El script que realizará el trabajo es este:

#!/usr/bin/env python
#geoip.py
import os
import GeoIP
import pyparsing as pp
import socket
import re
log_path = 'ipeses.dat'

if os.path.exists(log_path):
    log = open(log_path, 'r')
else:
    log = open('/var/log/messages', 'r')
os.remove("ip.blacklist")
os.remove("ipeses.info")
geo = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
octet = pp.Word(pp.nums, min=1, max=3)
ip_matcher = pp.Combine(octet + ('.' + octet) * 3)
file1 = open("ip.blacklist","w")
file2 = open("ipeses.info","w")
jail="sshd"
action="DROP"

for line in log:
        match = ip_matcher.searchString(line)
        if match:
                esip = re.search(r'((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9] |[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9] |[01]?[0-9][0-9]?))', line)
                if esip:

                        ip = match.pop()[0]
                        code = geo.country_code_by_addr(ip)
                        name = geo.country_name_by_addr(ip)
                        hostname = ""
                        try:
                            hostname = socket.gethostbyaddr(ip)[0]
                        except socket.error:
                                hostname = "NOT FOUND"
                                pass
                        if code != "ES":
                                print "%s:%s:%s:%s" % (code, name, hostname, ip)
                                file2.write("%s:%s:%s:%s\n" % (code, name, hostname, ip))
                                file1.write("%s:%s:%s\n" % (jail, ip, action))

file1.close()
file2.close()

Cuya salida produce algo así:

Si generamos ipeses.info con la salida anterior (puede tardar horas en generarse, por cada IP ha de realizar una consulta a una base de datos local, y una consulta DNS para intentar averiguar el nombre de host), podemos generar un fichero ip.blacklist para alimentar fail2ban en el siguiente apartado. Por ejemplo, nos quedamos solo con la IP:

cat ipeses.info|cut -f2 -d:|cut -f2 -d,

Pero si esto lo “enbuclamos” podemos generar un fichero del tipo:

apache-badbots:115.160.171.42:REJECT --reject-with icmp-port-unreachable
apache-postflood:94.76.82.164:REJECT --reject-with icmp-port-unreachable
apache-badbots:194.87.147.74:REJECT --reject-with icmp-port-unreachable
apache-postflood:89.218.84.214:REJECT --reject-with icmp-port-unreachable

Con el siguiente Script:

cat ipeses.info|cut -f2 -d:|cut -f2 -d, > soloips.tmp
jail="sshd"
accion="DROP"
while read ip
do
 echo $jail:$ip:$accion
done < soloips.tmp

rm soloips.tmp
fail2ban logo

fail2ban logo

Fase 2: Integración con fail2ban

Por lo que respecta a fail2ban genero el fichero ip.blacklist, que en cada reinicio del servicio fail2ban, carga haciendo persistente la lista de baneos, todas las IP’s contra el filtro ssh que lo he hecho de amplio espectro utilizando un rango de puertos 0:65535 (todos).

fail2ban sospedia.net

fail2ban sospedia.net

Los fichero de fail2ban a tocar para cargar una lista de IP bloqueadas en el reinicio del servicio, o del propio servidor, son estos:

jail.local

Aunque existe otro fichero /etc/fail2ban/jail.conf, se utiliza para opciones globales, si no existe jail.local podemos partir de una copia de este otro para personalizar al gusto. Tanto en uno como en otro existirá una sección [DEFAULT] y varias secciones, tantas como jaulas, por ejemplo la siguiente para monitorizar los LOGS del servicio SSH:

[sshd]
enabled     = true
filter      = sshd
action      = iptables[name=ssh, port="0:65535", protocol=tcp]
              %(mta)s[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
logpath     = /var/log/auth.log

En la sección [DEFAULT] podemos especificar parámetros como número de intentos erróneos antes del bloqueo, tiempo de bloqueo, una lista blanca de IP’s, una dirección de email donde lleguen avisos (destemail), etc… Para mandar un email cada vez que se bloquee una IP, interesante en modo depuración hay que incluir en la action de jail.local algo así:

%(mta)s[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]

Cada jaula que configuremos, al menos las que tengamos enabled, tendrán asociados 2 ficheros más, un filtro donde se recogen las expresiones regulares de los intentos de ataque (FAILREGEX), y las expresiones regulares de las consideradas exclusiones (IGNOREREGEX), uno en la carpeta filters.d/ y otro fichero que define la acción a desencadenar cuando ocurra algo en la carpeta actions.d/, que definirá que hacer cuando:

1) Se inicie la jaula
2) Se bloquee una IP
3) Se des-bloquee una IP
4) Se pare la jaula

El fichero a editar en filters.d/ viene dado por el nombre que tiene entre corchetes la jaula en jail.local y el del action.d/ por el del parámetro action de jail.local para esta jaula. Así pues en este caso podríamos tocar cosas de action.d/iptables-multiport.conf y de filter.d/sshd.conf para afinar nuestra jaula [sshd].

action.d/iptables-multiport.conf

Aquí se modifican las acciones start, ban y unban, usando el fichero ip.blacklist para conseguir la persistencia entre reinicios del servidor.

actionstart = iptables -N fail2ban-<name>
 iptables -A fail2ban-<name> -j RETURN
 iptables -I <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
 cat /etc/fail2ban/ip.blacklist|grep <name> | while IFS=: read Servicio IP Accion; do iptables -I fail2ban-$Servicio 1 -s $IP -j $Accion ; done
...
actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
 echo <name>:<ip>:<blocktype> >> /etc/fail2ban/ip.blacklist
...
actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
 echo <name>:<ip>:<blocktype> >> /etc/fail2ban/ip.blacklist.unbaned

Me pareció también interesante registrar los “unbaneos” en otro fichero ip.blacklist.unbaned.

Ficheros que hemos tocado:

jail.local (Debian en /etc/fail2ban/)
iptables-multiport.conf (Debian en /etc/fail2ban/action.d/)

Luego tocaremos también el filtro asociado a esta misma jaula [sshd] y editaremos el fichero filter.d/sshd.conf para optimizar un poco el rendimiento de la jaula, al excluir del análisis funcionamiento normal del servidor, que provoca registros en los logs por parte de demonios del sistema.

RECORDAD: Cada vez que toquemos la configuración de fail2ban podemos, como servicio que es:

service fail2ban restart

O bien stop | status | start

En algunas distros deberéis hacer:

/etc/init.d/fail2ban status

RECORDAD: Pero si lo que habéis tocado es una jaula, podéis recargar solo esa jaula:

fail2ban-client reload sshd

iptables

Puedes hacerte una idea muy práctica aquí: Manual práctico de iptables

Ahora comentamos las que aparecen en el fichero iptables-multiport.conf:

En el action start del fichero usamos estas:


actionstart = iptables -N fail2ban-<name>
 iptables -A fail2ban-<name> -j RETURN
 iptables -I <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
 cat /etc/fail2ban/ip.blacklist|grep <name> | while IFS=: read Servicio IP Accion; do iptables -I fail2ban-$Servicio 1 -s $IP -j $Accion ; done

En el bucle, para añadir todas las IP del fichero de datos ip.blacklist añadimos una iptable como esta:

iptables -I fail2ban-$Servicio 1 -s $IP -j $Accion

En el action ban usamos estas:

actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>
 echo <name>:<ip>:<blocktype> >> /etc/fail2ban/ip.blacklist

Para que haya persistencia añadimos al final del fichero ip.blacklist la nueva ip baneada y el servicio (name) que lo provocó.

Aquí <name>, <blocktype> e <ip> son variables que pone a nuestra disposición el framework de fail2ban y que podemos usar a nuestro antojo.

Por último, en el action unban usamos estas:

actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>
 echo <name>:<ip>:<blocktype> >> /etc/fail2ban/ip.blacklist.unbaned

Además de borrar la regla iptable (iptable -D [Delete]), si queremos tener un histórico de las IP que el propio fail2ban “baneó” y pasado el tiempo “unbaneó” (parámetro bantime en jail.local o incluso a nivel de jaula).

Banear / Unbanear IP’s de forma manual

En las versiones 0.8x de fail2ban no funciona el cliente en este sentido:

fail2ban-client set ssh-iredmail banip A.B.C.D

Lo haremos usando el propio comando iptables.

Si nos fijamos en las reglas para banear y unbanear de los ficheros de configuración de fail2ban tenemos:

actionban = iptables -I fail2ban-<name> 1 -s <ip> -j <blocktype>

y

actionunban = iptables -D fail2ban-<name> -s <ip> -j <blocktype>

Por ello podemos deducir que:

  • Para banear una ip A.B.C.D
iptables -I fail2ban-ssh 1 -s A.B.C.D -j DROP
  • Para unbanear una ip A.B.C.D
iptables -D fail2ban-ssh -s A.B.C.D -j DROP

El comando iptables I (Inserta) un DROP en la cadena (CHAIN) fail2ban-ssh, o bien D (Delete), aquellas reglas que tengan como -s (Source) la ip A.B.C.D.

Listado de iptables

Para conocer las cadenas iptables podéis usar varios comandos:

iptables -L -n

Otro que uso mucho es 

iptables -S

Es conveniente utilizar la opción -n de lo contrario se hará resolución de nombres lo que ralentizará considerablemente la respuesta.
Probad si queréis (se puede detener con CTLR+C):

iptables -L

Podemos obtener un listado con números de línea así:

iptables -vnL --line-numbers

Con este otro puedes inspeccionar en tiempo real lo que pasa en tu servidor:

watch -d 'iptables -vnL --line-numbers'

Chains & Jails

El cliente fail2ban utiliza el concepto de jaula (JAIL) en lugar de cadena (CHAIN).
Se puede ver el estado de jaulas activas así:

fail2ban-client status

Donde se apreciará que jaulas tenemos activas (enabled=true en jail.local):

root@server:~# fail2ban-client status
Status
|- Number of jail:	9
`- Jail list:		roundcube, apache-noscript, apache-badbots, ssh, apache, postfix, dovecot, apache-phpmyadmin, apache-overflows

Podéis interrogar por una jaula en concreto así:

root@server:~# fail2ban-client status ssh
Status for the jail: ssh
|- filter
|  |- File list:	/var/log/auth.log 
|  |- Currently failed:	0
|  `- Total failed:	1
`- action
   |- Currently banned:	0
   |  `- IP list:	
   `- Total banned:	0

Optimizar los filtros fail2ban

Podemos optimizar los filtros evitando analizar actividad normal en el servidor, como los inicios de sesión de los demonios.
Para ello y centrándonos como ejemplo en la jaula SSH con la que estamos jugando hoy, echaremos un vistazo al filtro sshd.conf, para lo que editaremos el fichero:

nano /etc/fail2ban/filter.d/sshd.conf 

E incluiremos esta lista de expresiones regulares a obviar:

ignoreregex = : pam_unix\((cron|sshd|systemd-user):session\): session (open|clos)ed for user (daemon|munin|postgres|root)( by \(uid=0\))?$
              : Successful su for (postgres) by root$
              New session \d+ of user (postgres)\.$
              Removed session \d+\.$

Para aplicar cambios sin hacer restart del servicio fail2ban:

fail2ban-client reload sshd

Aquí recordad se utiliza el nombre de la jaula, lo que sale al ejecutar fail2ban-client status).

Testear una jaula

Podemos testear una jaula / filtro fail2ban mediante la utilidad fail2ban-regex, con este comando por ejemplo:

fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf /etc/fail2ban/filter.d/sshd.conf | less

Donde según man fail2ban-regex, le estamos diciendo que testee el fichero de log /var/log/auth.log (inicios de sesión), usando como expresión regular FAIL que contiene el fichero de configuración /etc/fail2ban/filter.d/sshd.conf, y aunque es opcional lo ponemos, usando como expresión regular IGNORE la que contiene el mismo fichero /etc/fail2ban/filter.d/sshd.conf.

Si queremos testear alguna jaula de apache:

fail2ban-regex /var/log/apache2/error.log /etc/fail2ban/filter.d/apache.conf /etc/fail2ban/filter.d/apache.conf

Ahora el log está en /var/log/apache2/error.log, y la configuración de expresiones regulares en /etc/fail2ban/filter.d/apache.conf

Chuleta fail2ban

 

service fail2ban restart | stop | status | start

O bien (según distro):

/etc/init.d/fail2ban restart | stop | status | start

Para hacer cosas con el servicio, como pararlo.

 

fail2ban-client reload sshd

Donde sshd nombre de jaula, la vuelve a cargar con la nueva configuración.

 

fail2ban-client set ssh-iredmail banip A.B.C.D

Para bloquear (ban) una IP en versiones 9.x o superiores.

 

fail2ban-client set ssh-iredmail unbanip A.B.C.D

Para des-bloquear una IP en versiones 9.x o superiores.

 

fail2ban-client status

Ver el estado de jaulas activas, sí funciona en versiones inferiores a 0.9.x

 

fail2ban-client status ssh

Estado de la jaula concreta sshd.

 

fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf /etc/fail2ban/filter.d/sshd.conf

Testea filtros de fail2ban

 

Chuleta iptables

 

iptables -L -n
iptables -L
iptables -vnL --line-numbers
iptables -S

Lista iptables de diferentes formas.

 

watch -d 'iptables -vnL --line-numbers'

Monitorizar iptables en tiempo real.

 

iptables -D fail2ban-ssh -s A.B.C.D -j DROP -> "Des-Banea" la IP A.B.C.D

Quita un bloqueo / unban.

 

iptables -I fail2ban-ssh 1 -s A.B.C.D -j DROP

Banea / bloquea una IP.

 

//## Editado 2018:

  1. El proceso de extracción de IP ahora se realiza con el comando egrep y una expresión regular procesando carpetas de forma recursiva.
  2. Todo el proceso de geolocalizacion y generacion de ficheros para alimentar fail2ban y la base de datos ipeses.info se han pasado a un solo script Python.

Espero que sirva!

4 comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.