8.9.2.4. Ataques contra la autenticación¶
Uno de los tipos de ataques más socorridos son aquellos destinados a la obtención de una clave acceso a un sistema, que hemos denominado aquí ataques contra la autenticación. De entre ellos, podemos distinguir:
El ataque de fuerza bruta, que se basa en la obtención de una clave mediante la prueba masiva de todas las combinaciones posibles. En la práctica se suelen limitar las combinaciones posibles restringiendo los caracteres posibles o la longitud mínima o máxima de la clave.
El ataque de diccionario, que consiste en reducir las pruebas a las palabras que se encuentran en un diccionario de palabras o a una variación sobre las mismas.
El ataque por deducción que consiste en intentar obtener la contraseña deduciéndola a partir de la información personal que se tiene del usuario (por ejemplo, la información GECOS o el propio nombre de cuenta).
El ataque por ingeniería social que consiste en engañar al usuario para que el mismo sea el que proporcione las claves de acceso. Un envenenamiento DNS o un envenamuiento ARP pueden ser parte de un ataque de este tipo.
Estudiaremos en este apartado los tres primeros tipos.
8.9.2.4.1. Métodos¶
Para poner en práctica un ataque de estas características debemos distinguir entre:
- Ataque local
En este caso disponemos del fichero con la clave ofuscada (mediante una función de hash generalmente) y el propósito es llegar a conocer cuál es la clave sin ofuscar. La suite más socorrida para esta tarea es john the ripper, aunque existen otras como hashcat.
Obsérvese que el ataque no se denomina local porque se realice contra la propia máquina, sino porque se dispone del fichero de contraseñas (p.e. porque se haya robado de un servidor) y el software de averiguación actúa sobre el propio fichero sin necesidad de hacer uso de ningún sistema de autenticación.
- Ataque remoto
Que consiste en la averiguación de la clave intentando autenticaciones sucesivas sobre el servicio remoto, muy comúnmente SSH o SMTP. Para esta tarea podemos usar una aplicación como THC-Hydra.
Echaremos un vistazo a cómo se realizan estos ataques para conocer las ténicas y ponen en práctica más eficazmente las contramedidas adecuadas.
8.9.2.4.1.1. John the Ripper¶
Hay paquete en debian para su instalación:
# apt install john
Advertencia
John the Ripper tiene dos versiones: la oficial que es la incluida en la distribución y la versión Jumbo, con ampliaciones de su comunidad de usuarios y que tiene algunos añadidos bastantes interesantes. Para no complicarnos usaremos la versión oficial incluida en el repositorio oficial de debian. No obstante, puede instalarse la versión de la comunidad usando los repositorios de Kali:
# cat > /etc/apt/sources.list.d/kali.list
deb http://http.kali.org/kali kali-rolling main contrib non-free
# cat > /etc/apt/preferences.d/kali
Explanation: Paquetes de auditoría procedentes de Kali Linux
Package: *
Pin: release o=Kali
Pin-Priority: 50
# apt update
# apt install -t o=kali john
Además, generaremos un fichero de contraseñas para nuestras pruebas con cuatro usuarios:
$ cat <<EOF > passwords.txt
tester1:$(openssl passwd -1 -salt a secreta)
tester2:$(openssl passwd -1 -salt a Secreta2.)
tester3:$(openssl passwd -1 -salt a tester3)
tester4:$(openssl passwd -1 -salt a Tester4)
tester5:$(openssl passwd -1 -salt a 1234)
EOF
El programa tiene cuatro modos de funcionamiento:
- single (ataque por deducción)
Toma el nombre de usuario y la información GECOS como base para intentar averiguar la contraseña. A esta información aplicará las reglas de transformación listadas en la sección
[List.Rules:Single]
del fichero de configuración (en principio,/etc/john/john.conf
).En nuestro fichero de contreseñas no disponemos de la información GECOS[1], así que sólo será posible usar el nombre de usuario. Es bastante evidente que con este modo podremos obtener las contraseñas de usuarios tester3 y tester4.
- wordlist (ataque de diccionario)
Realiza un ataque con el diccionario que proporcionemos. A las palabras del diccionario podremos aplicarles transformaciones (añadir números, signos de puntuación, etc.) a fin de mejorar la efectividad. Estas reglas de transformación se listan en la sección
[List.Rules:Wordlist]
del ficheron de configuración.Con un diccionario de palabras castellanas podríamos obtener las contraseñas de los otros dos usuarios.
Nota
Las dos técnicas single y wordlist comparten la sintaxis para definbir las reglas de transformación. De hecho, podríamos considerar el ataque single como un ataque de diccionario en que, para cada usuario, el diccionario está compuesto únicamente por el nombre de usuario (y la información gecos si la hay).
- incremental (ataque de fuerza bruta)
Realiza un ataque de fuerza bruta en sí, aunque estableciendo una serie de reglas (caracteres válidos, longitud mínima, longitud máxima). Podríamos intentar este último ataque para averiguar la contraseña de tester5.
- external (estilo \»libre\»)
Utiliza para la generación de palabras una función escrita en un subconjunto del lenguaje C.
Empecemos por aplicar el ataque single:
$ john -single passwords.txt
Created directory: /root/.john
Loaded 5 password hashes with 2 different salts (md5crypt [MD5 32/64 X2])
Press 'q' or Ctrl-C to abort, almost any other key for status
tester3 (tester3)
Tester4 (tester4)
2g 0:00:00:00 100% 4.651g/s 5869p/s 5874c/s 9797C/s tester51901..tester51900
Use the "--show" option to display all of the cracked passwords reliably
Session completed
$ john -show passwords.txt
tester3:tester3
tester4:Tester4
2 password hashes cracked, 3 left
Bien, hemos logrado averiguar 2 de las 5 contraseñas: aquellas que eran muy semejantes al nombre de usuario. Pasemos a realizar un ataque de diccionario[2] y, de paso, hagamos trampa ya que conocemos de antemano las contraseñas. Por un lado restringamos mucho el diccionario a unas pocas palabras:
$ grep secret /usr/share/dict/spanish >> /tmp/words.list
y, por otro, apliquemos unas reglas de transformación bastante simplificadas. El modo de crear estas reglas diferenciados, depende de si usamos la versión oficial o la Jumbo:
- Versión oficial
Las reglas se encuentran definidas en la sección
[List.Rules:Wordlist]
y no hay forma de definir reglas con otro nombre distinto y aplicarlas. Lo más limpio, en este caso, es crear un fichero alternativo de configuración:$ cat > ~/.john/john.conf [List.Rules:Wordlist] : cAz"[0-9][.#_\-]"
en que se define esta sólo regla.
Advertencia
Hecho esto, john dejará de leer el fichero original
/etc/john/john.conf
, por lo que al acabar este tipo de ataque deberíamos borrar el fichero creado o moverlo a otra ubicaciónLas reglas de transformación posibilidad que para cada palabra, se pruebe:
La palabra sin transformar.
La palabra con la primera en mayúscula (c) y la adición al final (Az) de una cadena compuesta por un número cualquiera y un signo.
Ahora podemos practicar el ataque de diccionario:
$ john -wordlist:/tmp/words.list -rules passwords.txt Loaded 5 password hashes with 2 different salts (md5crypt [MD5 32/64 X2]) Remaining 3 password hashes with 2 different salts Press 'q' or Ctrl-C to abort, almost any other key for status secreta (tester1) Secreta2. (tester2) 2g 0:00:00:00 100% 5.405g/s 2216p/s 2708c/s 2713C/s Vicesecretaría..Vicesecretario9 Use the "--show" option to display all of the cracked passwords reliably Session completed $ john -show passwords.txt tester1:secreta tester2:Secreta2. tester3:tester3 tester4:Tester4 4 password hashes cracked, 1 left
- Versión Jumbo
En esta versión sí podemos crear nuevas secciones con otras reglas y aplicarlas. Para ello, lo más conveniente es añadir un
.include
al fichero de configuración para que podamos añadir nuestras reglas personales aparte:# cat >> /etc/john/john.conf .include "~/.john/john-local.conf"
Nota
Deje una línea en blanco antes y detrás del
.include
Hecho lo cual podemos hacer nuestros añadidos en tal fichero:
# cat > ~/.john/john-local.conf [List.Rules:MisReglas] : cAz"[0-9][.#_\-]"
que podemos comprobar que están disponibles obteniendo la lista completa de reglas:
# john -list=rules | grep -i MisR misreglas
En este caso, el ataque se hace exactamente igual que en la versión oficial, pero indicando qué conjunto de reglas quieren aplicarse:
$ john -wordlist:/tmp/words.list -rules:misreglas passwords.txt
Nota
Tanto para este tipo de ataque como para el siguiente, en el que se
generan automáticamente contraseñas, podemos comprobar cuáles son las
generadas añadiendo la opción -stdout
y eliminando el fichero objetivo:
$ john -wordlist:/tmp/words.list -rules -stdout
$ john -incremental:Digits -stdout
Por último, ilustraremos un ataque incremental usando digitos como caracteres válidos:
$ cat >> ~/.john/john.conf
[Incremental:Digits]
File = $JOHN/digits.chr
MinLen = 3
MaxLen = 10
CharCount = 10
En realidad, esta definición (aunque con distinto mínimo y máximo) ya está hecha
en el fichero de configuración /etc/john.john.conf
, pero establecerla
nosotros mismos en nuestro fichero de configuración nos permite echar un vistazo
a como se construyen estos ataques de fuerza bruta. Además, de no obrar así,
deberiamos deshacernos de ~/.john/john.conf
ya que su existencia, anula
la lectura del fichero general. Aplicando este ataque incremental:
$ john -incremental:Digits passwords.txt
Loaded 5 password hashes with 2 different salts (md5crypt [MD5 32/64 X2])
Remaining 1 password hash
Press 'q' or Ctrl-C to abort, almost any other key for status
1234 (tester5)
1g 0:00:00:00 5.000g/s 1340p/s 1340c/s 1340C/s 1234..123321
Use the "--show" option to display all of the cracked passwords reliably
Session completed
$ john -show passwords.txt
tester1:secreta
tester2:Secreta2.
tester3:tester3
tester4:Tester4
tester5:1234
5 password hashes cracked, 0 left
Nota
Con la versión Jumbo, el software también es capaz de atacar contraseñas de ficheros rar o claves simétricas de claves SSH o certificados en formato PEM.
Nota
John the Ripper puede usarse como herramienta para comprobar la robustez de las contraseñas de nuestros usuarios y enviar un mensaje de correo a aquellos usuarios de los que hayamos sido capaces de averiguar la contraseña. Para ello la suite trae un script llamado mailer que automatiza la tarea.
8.9.2.4.1.2. THC-Hydra¶
Cuando no se dispone del fichero de contraseñas, es obvio que el único modo de averiguar una, es intentado repetidamente la autenticación hasta dar con una pareja usuario/contraseña que permita el acceso. THC-Hydra permite automatizar los intentos de acceso a distintos tipos de servidores (entre ellos FTP, IMAP, MySQL, SMTP o SSH).
A diferencia del caso anterior, el objetivo no es sólo averiguar contraseñas, sino también usuarios, por lo que el programa permite tanto facilitar el usuario como la contraseña. Por ello:
-l <usuario>
Permite facilitar el nombre de usuario.
-L <fichero>
Permite facilitar la lista de usuarios expresada en el fichero indicado (un usuario por línea).
-p <contraseña>
Permite facilitar una contraseña.
-P <fichero>
Permite facilitar la lista de contraseñas expresada en el fichero indicado (una contraseña por línea).
-C <fichero>
Permite facilitar la lista de tuplas usuario/contraseña expresada en el fichero indicado (cada línea debe ser de la forma
usuario:contraseña
).-x <min:max:charset>
Genera todas las combinaciones posibles entre una longitud mínima y una longitud máxima de los caracteres definidos con charset. En la definición del charset «1» representan digitos, «a» letras minúsculas, «A» letras mayúsculas y cualquier otro caracter debe expresarse explícitamente. Por tanto,
-x '4:8:a1.-'
generará todas las claves posibles de entre 4 y 8 caracteres que contengan letras minúsculas, números, puntos y guiones.-e <nsr>
Permite añadir a las pruebas de contraseña que provequen
-p
,-P
o-x
, la contraseña nula (n), la contraseña que coincida con el login (s) y la contraseña que sea el login puesto del revés (r). Así, por ejemplo,-e ns
probaría como contraseña la vacía y el login.
Así pues, -p
, -P
y -C
nos permiten realizar ataques de diccionario y
-x
ataques de fuerza bruta con restricciones (equivalentes a los
incrementales de john).
Si se facilitan varios usuarios y varias contraseñas se probarán todas las
combinaciones posibles, de modo que para el primer usuarios se prueban todas las
contraseñas, para el segundo, todas; y así sucesivamente. Si quiere hacerse la
combinación de manera que para la primera contraseña se prueben todos los
usuarios, para la segunda, todos, etc. puede añadirse la opción -u
.
Otras opciones de interés son:
-f
Abandona el ataque cuando se consigue obtener la primera pareja usuario/contraseña.
-t N
Fija el número de intentos simultáneos que, por defecto, es 16.
Con estos mimbres es fácil hacer un ataque:
$ hydra -l usuario -P /tmp/passwords.txt -e nsr -t 4 -vV VICTIMA ssh
o si se prefiere:
$ hydra -l usuario -P /tmp/passwords.txt -e nsr -t 4 -vV ssh::/VICTIMA
Nota
¿Hay alguna manera de tomar un diccionario y probar no sólo las
palabras que contiene, sino también otras contraseñas generadas a partir de
esas palabras? Algo así a lo que hace john al añadir
-rules
. Desgraciadamente, no. O no al menos exclusivamente con
hydra. Sí podemos, sin embargo, generar las transformaciones con
john y su opción -stdout
y el diccionario resultante pasárselo
a hydra, haciendo uso de una tubería:
$ john -wordlist:/tmp/words.list -rules -stdout | hydra -l usuario -P /dev/fd/0 -e nsr -t 4 -vV ssh::/VICTIMA
8.9.2.4.2. Contramedidas¶
De los métodos de ataque anteriores, debemos de entresacar dos enseñanzas:
Hemos de evitar el robo de ficheros de contraseñas, aunque éstas estén ofuscadas, ya que existen herramientas como john para intentar averiguarlas.
Deben evitarse los nombres de usuarios que por defecto nos sugieran las aplicaciones, porque son esos usuarios los que suelen probarse con herramientas como hydra.
Hemos de defender los servicios accesibles de ataques automatizados como los realizados por hydra. Enfocaremos esta sección en ver cómo hacerlo mediante dos alternativas:
Ocultamiento del servicio mediante port-knocking, de manera que el servicio será accesible sólo si previamente se ha dado el «santo y seña».
Veto al acceso para aquellas máquinas cuya actividad las vuelva sospechosas de estar intentando un ataque contra la autenticación. Para practicar este veto, linux provee dos mecanismos:
- hosts.deny
Toda dirección IP listada en
/etc/hosts.deny
no podrá acceder a ningún servicio que use TCP Wrappers <https://es.wikipedia.org/wiki/TCP_Wrapper>. El mecanismo es poco versátil, porque se denegará el acceso a todos los servicios que lo usen y no a algunos individuales, y porque los servicios ajenos al mecanismo no se verá afectados en ningún caso. Para conocerse si un servicio usa este mecanismo basta con comprobar si el ejecutable enlaza con la libreríalibwrap.so
:$ ldd /usr/sbin/sshd | grep -q 'libwrap.so' && echo "SI" || echo "NO" SI $ ldd /usr/sbin/nginx | grep -q 'libwrap.so' && echo "SI" || echo "NO" NO
Por tanto, este método serviría para impedir el acceso al servidor SSH, pero no al servidor web. Este mecanismo de veto era el que usaba denyhosts[3].
Cortafuegos (por lo general, iptables o nftables). Consiste en crear reglas para las máquinas sospechosas que impidan el acceso a los servicios disponibles. El mecanismo es mucho más versátil ya que se podrá aplicar a servicios individuales y será aplicable a todo servicio[4]. Las soluciones con el módulo recent de iptables y fail2ban usan esta técnica.
Nota
Se use uno u otro mecanismo, las listas de direcciones vetadas son dinámicas, así que debe habilitarse algún medio para gestionarlas.
8.9.2.4.2.1. IPtables¶
El módulo recent de iptables es muy apropiado tanto para limitar los atanques de fuerza bruta como para implementar la técnica del port-knocking.
8.9.2.4.2.1.1. Limitación de accesos¶
Es obvio que en condiciones normales un uso legítimo de algunos de nuestros servicios no implica que una misma dirección IP abra muchas conexiones en un corto espacio de tiempo. Por ejemplo, al servidor SSH accederá de vez en cuando un usuario por lo que muy de vez en cuando se abrirá una conexión al puerto SSH. Con el servicio SMTP que escucha en los puertos 587 o 465 ocurrirá básicamente lo mismo: un usuario, cada cierto tiempo, mandará un mensaje, pero es bastante improbable que lo haga repetidamente en un corto espacio de tiempo.
Nuestro propósito, pues, es vetar el acceso a aquellas direcciones que, durante un espacio de tiempo que establezcamos, sobrepasen un umbral de conexiones nuevas, porque si lo hacen, sospechamos que se trata de intentos automatizados de acceso que pretenden encontrar una pareja usuario/contraseña válida.
Veamos cómo asegurar los servicios SSH y SMTP de ataques de fuerza bruta para impedir que desde una misma IP se pueda abrir más de 6 conexiones nuevas en un periodo menor a 5 minutos:
# Para desechar paquetes registrando el rechazo
iptables -N LOGDROP
iptables -A LOGDROP -j LOG --log-level warning --log-prefix "Ataque fuerza bruta: "
iptables -A LOGDROP -j DROP
# Anti ataques de fuerza bruta contra SSH
iptables -N SSH
iptables -A SSH -m recent --name brute-ssh --update --seconds 300 --hitcount 6 --reap -j LOGDROP
iptables -A SSH -m recent --name brute-ssh --set -j ACCEPT
# Anti ataques de fuerza bruta contra SMTP
iptables -N SMTP
iptables -A SMTP -m recent --name brute-smtp --update --seconds 300 --hitcount 6 --reap -j LOGDROP
iptables -A SMTP -m recent --name brute-smtp --set -j ACCEPT
iptables -A INPUT -p tcp --dport ssh --syn -m conntrack --ctstate NEW -j SSH
iptables -A INPUT -p tcp -m multiport --dport urd,submission --syn -m conntrack --ctstate NEW -j SMTP
Nota
Suponemos que seguimos una política de lista blanca y que por el puerto 25 no se realizan conexiones autenticadas, tal como se propugna aquí.
Obsérvese, porque es importante que, usando esta estrategia, el tiempo de comprobación del accesos y el tiempo de veto debe coincidir. En nuestra propuesta, son 5 minutos. Cuanto mayor sea este tiempo, mayor es el tiempo de veto, pero también mayor el número de falsos positivos. Por tanto, una mejora a lo anterior consiste en independizar ambos tiempos, de modo que el de comprobación podamos reducirlo (por ejemplo, 30 segundos) y el de veto podamos ampliarlo (por ejemplo, 10 minutos). Para ello podemos crear dos conjuntos distintos: uno que apunte accesos y otro que apunte vetos (banned). Por ejemplo:
# Para desechar paquetes registrando el rechazo
iptables -N LOGDROP
iptables -A LOGDROP -j LOG --log-level warning --log-prefix "Ataque fuerza bruta: "
iptables -A LOGDROP -m recent --name banned --set -j DROP
# Anti ataques de fuerza bruta contra SSH
iptables -N SSH
iptables -A SSH -m recent --name access-ssh --update --seconds 30 --hitcount 5 --reap -j LOGDROP
iptables -A SSH -m recent --name access-ssh --set -j ACCEPT
# Anti ataques de fuerza bruta contra SMTP
iptables -N SMTP
iptables -A SMTP -m recent --name access-smtp --update --seconds 30 --hitcount 5 --reap -j LOGDROP
iptables -A SMTP -m recent --name access-smtp --set -j ACCEPT
iptables -A INPUT -m conntrack --ctstate NEW -m recent --name banned --update --seconds 600 --reap -j DROP
iptables -A INPUT -p tcp --dport ssh --syn -m conntrack --ctstate NEW -j SSH
iptables -A INPUT -p tcp -m multiport --dport urd,submission --syn -m conntrack --ctstate NEW -j SMTP
En la propuesta, los accesos a los distintos servicios se apuntan por separado (access-ssh, access-smtp), así que se pueden establecer distintas limitaciones según el servicio, pero una vez que un cliente alcanza la limitación en uno de ellos, se apunta en una lista única (banned): eso facilita las reglas a costa de que el tiempo de veto sea uno con independencia de por qué puerto se haya recibido el ataque. Además, el cliente vetado no podrá acceder a ningún servicio y, si lo intenta durante el tiempo de veto, se renovará el tiempo de veto.
8.9.2.4.2.1.2. Port-knocking¶
Port knocking (golpeteo de puertos, en castellano) es una técnica que permite abrir desde el exterior en el firewall el puerto en el que escucha algún servicio mediante una secuencia preestablecida de intentos de conexión a puertos cerrados. Con ello se logra que un escaneo de puertos no detecte los servicios realmente ofrecidos.
En consecuencia, el acceso al servicio oculto se realiza del siguiente modo:
En principio, el cortafuegos impiede el acceso al servicio.
Un cliente que conoce la secuencia que permite abrir el puerto de escucha, la lleva a cabo.
El servicio de port-knocking intercepta esta secuencia y, sin aviso al cliente (nunca lo hay) abre durante un tiempo limitado el acceso a ese cliente.
El cliente realiza la conexión al servicio.
Para ponerlo en práctica puede usarse un servicio independiente como knockd, pero aquí trataremos de hacerlo exclusivamente con iptables.
Para nuestro ejemplo, el golpeteo será de tres golpes: el primero al puerto 11111/UDP, el segundo al puerto 22222/UDP y el tercero al puerto 33333/UDP.
# Las máquinas dan el primer toque (debe ser al puerto 11111/UDP)
iptables -N toc
iptables -A toc -p udp --dport 11111 -m recent --set --name toc -j DROP
# Las máquinas dan el segundo toque (debe ser al puerto 22222/UDP)
iptables -N toctoc
iptables -A toctoc -p udp --dport 22222 -m recent --set --name toctoc -j DROP
# Las máquinas dan el tercer toque (debe ser al puerto 33333/UDP)
iptables -N toctoctoc
iptables -A toctoctoc -p udp --dport 33333 -m recent --set --name toctoctoc -j DROP
# Máquinas con el paso concedido
iptables -N paso
iptables -A paso -p tcp --dport ssh -j ACCEPT
# ... y otros posibles servicios también ocultos
iptables -A INPUT -m recent --remove --name toctoctoc -j paso
iptables -A INPUT -m recent --remove --name toctoc -j toctoctoc
iptables -A INPUT -m recent --remove --name toc -j toctoc
iptables -A INPUT -j toc
Obsérvese que cada vez que damos un toque, borramos el toque anterior (p.e. al hacer «toctoc» borramos «toc») y que finalmente tras «toctoctoc» el siguiente paquete enviado borra el último «toctoctoc». En consecuencia, para conectar al servicio debemos mandar en orden y exclusivamente «toc», «toctoc», «toctoctoc» y el paquete que inicia la conexión. Si no seguimos la secuencia o intercalamos otro paquete entre medias, la apertura se malogrará.
Nota
Suponemos una política de lista blanca y que todas estas reglas sustituyen a la repla única:
iptables -p tcp --dport ssh -j ACCEPT
es obvio que nuestro conjunto de reglas deberá incluir todas las demas necesarias (permitir el tráfico de la interfaz loopback, permitir el tráfico establecido, etc.)
De este modo, para que una máquina pudiera acceder al servicio SSH debería
antes hacer lo siguiente
:
#!/bin/sh
#
# Accede al servicio SSH, tocando primero
TOC="11111 22222 33333"
get_server() {
ssh -G "$@" | awk '$1=="hostname" {print $2}'
}
server=$(get_server "$@")
for p in $TOC; do
printf "toc... "
echo "x" | nc -u -q1 "$server" "$p"
done
echo
ssh "$@"
Nota
Si decidimos abrir el servicio con el golpeo del mismo puerto un número determinado de golpes (en el ejemplo, 3), podemos simplificar las reglas:
#!/bin/sh
# Repique en el puerto
iptables -N toc
iptables -A toc -p udp --dport 11111 -m recent --set --name toc -j DROP
iptables -A toc -m recent --remove --name toc
# Ya repicado lo suficiente
iptables -N paso
iptables -A paso -m recent --remove --name toc
iptables -A paso -p tcp --dport ssh -j ACCEPT
# ... y otros posibles servicios también ocultos
iptables -A INPUT -m recent --rcheck --hitcount 3 --name toc -j paso
iptables -A INPUT -j toc
8.9.2.4.2.2. nftables¶
Como habrá que ir jubilando el viejo iptables, es preciso buscar alternativas con nftables.
8.9.2.4.2.2.1. Limitación de accesos¶
Mediante el uso de la limitación del caudal y los conjuntos dinámicos, puede definirse una limitación efectiva. Es conveniente, además, que lea las consideración vertidas en el epígrafe correspondiente para la implementación con iptables, porque son pertinentes también para nftables.
Como en aquel epígrafe, prepararemos una solución para proteger los servicios
SMTP y SSH. Partiremos de que tomamos como base la política de lista
blanca propuesta para la resolución de los casos de uso
a la cual añadimos este fíchero que implementa la defensa
:
# /etc/nftables.d/01-antibruta.nft
#
# Configuración contra ataques de fuerza bruta.
#
table ip filter {
set banned {
type ipv4_addr
size 4095
timeout 5m
}
chain BANNED {
type filter hook input priority filter-5
ct state new ip saddr @banned counter reject comment "Veto a atacantes de fuerza bruta"
}
chain PROTECTOR {
ct state new meter test size 65535 {ip saddr timeout 1m limit rate 3/minute burst 3 packets} accept
ct state new add @banned
}
}
Posteriormente, en el fichero en que definamos los servicios accesibles podemos escribir algo como esto:
# /etc/nftables/90-whitelist.nft
table ip filter {
set protected_ports {
type inet_service
flags constant
elements = {
ssh,
urd,
submission
}
}
chain OUTPUT {
ct state {established, related} accept
}
chain INPUT {
ct state {established, related} accept
tcp dport @protected goto PROTECTOR comment "Servicios protegidos contra fuerza bruta"
tcp dport {http, https} accept comment "Servicio WEB" # Servicio no protegido
}
}
8.9.2.4.2.2.2. port-knocking¶
8.9.2.4.2.3. fail2ban¶
fail2ban (página web) es una aplicación escrita en python que escruta los accesos a distintos servicios, identifica cuáles de ellos son fallidos y, aplicando ciertas reglas de tolerancia (p.e. 5 accesos fallidos en los últimos 5 minutos desde una misma dirección) impide el acceso al servidor desde la dirección desde la que se realizan tales accesos durante un espacio de tiempo determinado. Para vetar el acceso crea dinámicamente reglas de iptables.
Por tanto, persigue el mismo objetivo que la limitación de accesos que practicamos con iptables, pero no del mismo modo, por lo que presenta ventajas y inconvenientes:
- Ventajas
Al escudriñar si el acceso ha tenido éxito, sólo contabiliza como accesos sospechos los fallidos. Por tanto, es más improbable que obtengamos con él falsos positivos.
Es una solución más general. El módulo recent no sirve para evitar ataques de fuerza bruta a servidores web, por ejemplo, ya que por la naturaleza de este protocolo, un cliente puede abrir muchas conexiones seguidas (o incluso simultáneas) con el servidor. Por tanto, limitar el acceso no es solución. En cambio, sí lo es detectar acceso fallidos.
- Desventajas
Debemos instalar software adicional y mantenerlo en ejecución.
El desempeño de la solución que usa el módulo recent de iptables es muchísimo mejor ya que, por su naturaleza, fail2ban debe hacer muchas más tareas y tareas más pesadas: cada cierto tiempo debe buscar en uno o varios registros accesos restringidos y añadir o eliminar reglas de iptables.
Cada servidor requiere una configuración de búsqueda de fallos diferente, puesto que registra los acceso fallidos con un formato diferente y, posiblemene, en un lugar diferente.
La instalación es sencilla puesto que se dispone de paquete en debian:
# apt install fail2ban
8.9.2.4.2.3.1. Estructura de la configuración¶
Toda la configuración se encuentra dentro de /etc/fail2ban
:
/etc/fail2ban/
|-- action.d
| |-- iptables.conf
| `-- ...
|-- fail2ban.conf
|-- fail2ban.d
|-- filter.d
| |-- sshd.conf
| `-- ...
|-- jail.conf
`-- jail.d
|-- sshd.conf
`-- ...
Las claves de esta configuración modular son las siguientes:
Los ficheros de configuración son ficheros ini con extensión .conf. Ahora bien, si en el mismo directorio se encuentra un fichero extensión .local, fail2ban leerá el fichero .conf primero y usará el fichero .local para añadir más opciones o sobreescribir las que incluyera .conf. Por tanto, es mejor no modificar nunca los ficheros .conf ya que son los que distribuye debian con su paquete.
El fichero
fail2ban.conf
incluye opciones para controlar cómo arranca el demonio (p.e. podríamos cambiar el nombre del fichero donde fail2ban apunta sus propios registros). En realidad, la definición de estas opciones no se circunscribe exclusivamente al fichero, sino que hay toda una estructura modular de ficheros que se leen en el siguiente orden:fail2ban.conf
.fail2ban.d/*.conf
, en orden alfabético.fail2ban.local
fail2ban.d/*.local
, en orden alfabético.
de manera que lecturas posteriores añaden nuevas opciones y sobreescriben las ya existentes.
El fichero
jail.conf
(y toda su estructura correspondiente) es lo que nos toca modificar fundamentalmente para ajustarlo a nuestro servidor. Aquí es donde se define cada jail, o sea, cada tarea de vigilancia que consiste en:Reconocer en la fuente correcta (un registro de syslog, por ejemplo) los intentos fallidos de conexión. El reconocimiento de estos intentos se logra aplicando expresiones regulares sobre los registros, la definición de las cuales se denomina filtro en la jerga de fail2ban.
En base a lo anterior, establecer cuáles son los ataques sufridos por la máquina. Lo habitual es que fail2ban considere ataque la existencia de una cantidad prefijada de conexiones fallidas en un periodo también prefijado de tiempo.
En caso de detectar uno o más ataques, desencadenar una acción de veto (la adición de una regla de iptables, por ejemplo).
Por tanto, la escritura de un jail supone definir:
Qué filtro queremos aplicar.
Qué condiciones se deben cumplir para encontrarnos ante un ataque. Por ejemplo, la opción
maxretry
indica el máximo número de intentos fallidos que toleramos ofindtime
el periodo de tiempo en que deben buscarse esos intentos.Qué acción debe desencadenarse. Por ejemplo,
iptables.conf
crea reglas de iptables,iptables-ipset-proto4.conf
crea reglas de iptables que aprovechan las capacidades del módulo set,hostsdeny.conf
añade entradas en/etc/hosts.deny
, etc.
En
action.d
se definen las acciones.En
filter.d
se definen los filtros.
8.9.2.4.2.3.2. Configuración básica¶
El paquete trae un fichero jail.conf
que incluye entre otras cosas
los valores de la configuración por defecto en la sección [DEFAULT]
. Esto
es así, porque el fichero es de tipo INI y cada jail se define en una
sección distinta.
Si echamos un ojo veremos que en la referida sección hay opción que dice:
maxretry = 6
Esto significa que, a menos que en un jail en particular redefinamos la opción con otro valor, para todos los servicios en umbral máximo de fallos que toleraremos será 6.
Empecemos por crear un jail.local
para redefinir a nuestro gusto las
opciones comunes:
[DEFAULT]
ignoreip = 127.0.0.0/8 192.168.0.0/16
bantime = 1200 ; 20m
findtime = 120 ; 2m
destemail = root@localhost
sendermail = root@localhost
Hemos modificado:
ignoreip
Para que se evite crear reglas de veto a máquinas locales.
bantime
Tiempo en segundo que se mantendrá vetada a la máquina.
findtime
Periodo de tiempo hacia atrás en que se restrean accesos fallidos.
destemail
,sendermail
Direcciones de correo que se usan como destinatario y remitente para el envío de avisos cuando se ha realizado un veto. fail2ban no tiene una opción que habilite o deshabilite estos mensajes, sino que está definida la acción
action.d/sendmail.conf
que no realiza veto alguno, sino que simplemente manda el aviso. Si nuestra intención es que el jail avise, deberá de definirse con dos acciones: la que propiamente realiza el veto y ésta.
Bien, pero ¿qué hay de los jails? Vale. Pues hagamos (o mejor, modifiquemos)
un jail para SSH. Si el servicio es habitual, es más que probable que ya
tenga una sección en la configuración. La de este servicio se llama [sshd]
y
estudiaremos bajo el próximo epígrafe por qué es así y por qué ya viene activo
este jail. Ahora nos limitaremos a establecer las opciones particulares para
este jail. Lo más adecuado es crear jail.d/ssh.local
con un contenido
como éste:
[sshd]
;enable = true
maxretry = 5
findtime = 90 ; 1.5m
Nota
No lo habilitamos, porque ya está activo.
8.9.2.4.2.3.3. Funcionamiento¶
Es obvio que cada vez que hagamos un cambio como en la configuración anterior, podremos reiniciar el servicio con la herramienta habitual de debian:
# invoke-rc.d fail2ban restart
Además, podemos comprobar de forma genérica cuales son los servicios protegidos o, más precisamente dicho, los jails activos:
# fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd
Para consultar el estado de un jail particular:
# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 2
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 192.168.1.241
8.9.2.4.2.3.4. Hurgando en la configuración¶
Hasta ahora, nos hemos limitado a modificar a tientas un jail ya creado
([sshd]
). Ahora bien, más allá de que por su nombre parezca que
efectivamente vigila el servicio SSH, ¿por qué realmente vigila tal servicio?
¿Cómo lo hace? ¿Cómo lo defiende? Para resolver las preguntas lo mejor es hacer
un poco de ingeniera inversa sobre la configuración. Si revisamos
jail.conf
descubriremos la siguiente sección:
[sshd]
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
en donde está claro que parece un jail que actúa sobre el puerto 22 (ya
veremos qué signifca eso). Por otro lado, se fija backend
que si seguimos la
madeja de variables nos lleva a un valor de «auto». Leyendo los comentarios en
el propio fichero llegaremos a la conclusión de que esto implica usar como
backend «polling», que significa que se leen registros del fichero indicado
con logpath
. De nuevo, siguiendo valores, veremos que en este caso tal
fichero es /var/log/auth.log
, valor que es correcto si está activo
syslog, como es el caso de Stretch o Buster.
Nota
Desde las últimas versiones de fail2ban es posible también
usar como backend para los registros systemd, aunque esto
exige definir la opción journalmatch
y no logpath
.
Bien, sabemos qué registros revisa fail2ban, pero ¿qué filtros usa
este jail? Es obvio que, si la respuesta no está en su sección, se tiene que
hallar en la sección [DEFAULT]
, y efectivamente así es:
filter = %(__name__)s
que implica de modo predeterminado que el filtro se llama como el nombre de la
sección, en este caso, sshd. Por tanto, el filtro se encuentra en
filter.d/sshd.conf
. Si echamos un ojo al filtro, encontraremos las
expresiones regulares que concuerdan con los mensajes de acceso fallido que el
demonio del servicio escribe en los registros. Además, está definida la opción
journalmatch
apropiada, de modo que hacer que el jail use
systemd en vez de syslog se limita a escribir:
[sshd]
# ... configuración personalizada ...
backend = systemd
Resuelto el asunto del filtro, ¿cuál es la acción que se lleva a cabo? La
respuesta de nuevo está en la sección [DEFAULT]
:
banaction = iptables-multiport
action_ = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s",
port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
action_mw = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s",
port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
%(mta)s-whois[name=%(__name__)s, sender="%(sender)s",
dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
# ... otras variantes de la acción ...
action = %(action_)s
La configuración está desgranada de esta forma para simplificar las modificaciones:
La opción que define cuál es la acción a realizar es
action
, que se apoya enaction_
, que a su vez usabanaction
. Una alternativa podría haber sido:[sshd] action = iptables-allports[name=%(__name__)s, chain=INPUT]
que veta a la máquina sospechosa el acceso a todos los puertos. En este caso, no es necesaria la inclusión de port o protocol.
La razón de la existencia de
action_
es que es necesario pasar a la acción algunos datos necesarios como el puerto, por ejemplo. Lo cierto es que se pasan parámetros muy generosamente, porque no todos son necesarios y pasar de más no provoca ningún fallo.Nota
El valor predeterminado de
chain
está definido enaction.d/iptables-common.conf
y es INPUT.La acción definida en
action.d/
que usaremos viene definida porbanaction
y seráaction.d/iptables-multiport.conf
. Si echamos un vistazo a ese fichero, es fácil darse cuenta de que para vetar se usa iptables usando el módulo multiport[5] y se veta exclusivamente el acceso a los puertos del servicio.Así, podríamos hacer:
[sshd] # ... otra configuración adicional ... banaction = iptables-allports
y todo funcionaría perfectamente, ya que esta acción requiere aún menos parámetros que iptables-multiport y, por tanto, el valor de
action_
seguirá siendo válido.
Otros
action_*
añaden alguna acción extra. Por ejemplo, si hiciéramos:[sshd] # ... otra configuración adicional ... action = %(action_mw)s
Además de vetar, se enviaría un mensaje de aviso cada vez que se produjera un veto.
Por último, afirmamos rotundamente al principio que [sshd]
estaba activo;
pero, ¿por qué es así? De hecho, en la sección [DEFAULT]
se dice sin
ambages:
enabled = False
La razón de ello es que jail.d/
viene de serie con un jail llamado
jail.d/defaults-debian.conf
con este contenido:
[sshd]
enabled = true
Por lo general, si los ataques son frecuentes es conveniente hacer[6]:
banaction = iptables-ipset-proto6
que usa el módulo ipset de iptables para gestionar la lista de direcciones IP vetadas.
Advertencia
Es bastante probable que ipset no esté instalado en su sistema:
# apt install ipset
Ejemplo de aplicación
Si quisiéramos vetar con /etc/hosts.deny
, en vez de hacerlo con el
cortafuegos y leer registros con systemd, podríamos ensayar la
siguiente configuración:
[sshd]
enabled = false
[sshd-denyhosts]
enabled = true
port = ssh
backend = systemd
filter = sshd
;banaction = hostsdeny[daemon_list=sshd]
banaction = hostsdeny
Advertencia
Usar /etc/hosts.deny
es sólo una excentricidad para jugar
un poco más con la configuración: no imite esta idea en un servisdor real. De
hecho, fail2ban no parece andar del todo fino usando este modo de
veto.
Notas al pie