Conjuntos de IP revisitados
Ya hay una respuesta que menciona los conjuntos de IP. Sin embargo, es más bien unidimensional porque se enfoca en las ganancias de rendimiento sobre las reglas clásicas y el hecho de que los conjuntos de IP mitigan el problema que se tiene con muchas direcciones IP individuales que no se pueden expresar fácilmente como una subred en notación CIDR.
Notación utilizada a continuación
Porque ipset
usaré la notación leída ipset restore
y escrita por ipset save
.
En correspondencia con iptables
(y ip6tables
) reglas, usaré la notación como leída iptables-restore
y escrita por iptables-save
. Esto hace una notación más corta y me permite resaltar posibles reglas solo IPv4 (prefijadas -4
) o solo IPv6 (prefijadas -6
).
En algunos ejemplos, desviaremos el flujo de paquetes a otra cadena. Se supone que la cadena existe en ese punto, por lo que no se producen las líneas para crear las cadenas (ni se menciona el nombre de la tabla ni los comandos COMMIT
al final).
Conjuntos de IP avanzados
Los conjuntos de IP pueden hacer mucho más de lo que se mencionó en la otra respuesta y definitivamente debe leer la documentación del conjunto de IP ( ipset(8)
) junto con iptables-extensions(8)
esta breve entrada aquí.
Por ejemplo voy a centrar principalmente en tres tipos de conjuntos: hash:ip
, hash:net
y list:set
, pero no son más que aquellos y todos ellos tienen casos de uso válidos.
Por ejemplo, también puede hacer coincidir los números de puerto, no solo las direcciones IP .
Guardar y restaurar conjuntos de IP como con iptables-save
yiptables-restore
Puede crear declaraciones de conjuntos de IP en masa e importarlas canalizándolas en ipset restore
. Si desea que su comando sea más resistente frente a entradas ya existentes, use ipset -exist restore
.
Si sus reglas están en un archivo llamado default.set
que usaría:
ipset -exist restore < default.set
Un archivo como ese puede contener entradas a create
conjuntos y a add
entradas en ellos. Pero, en general, la mayoría de los comandos de la línea de comandos parecen tener una versión correspondiente en los archivos. Ejemplo (crear un conjunto de servidores DNS):
create dns4 hash:ip family inet
create dns6 hash:ip family inet6
# Google DNS servers
add dns4 8.8.8.8
add dns4 8.8.4.4
add dns6 2001:4860:4860::8888
add dns6 2001:4860:4860::8844
Aquí se crea un conjunto para IPv4 ( dns4
) y uno para IPv6 ( dns6
).
Tiempos de espera en conjuntos de IP
Los tiempos de espera en conjuntos de IP pueden establecerse como predeterminados por conjunto y también por entrada. Esto es muy útil para escenarios en los que desea bloquear a alguien temporalmente (por ejemplo, para escanear puertos o intentar forzar por fuerza bruta su servidor SSH).
La forma en que funciona es la siguiente (predeterminado durante la creación de conjuntos de IP):
create ssh_loggedon4 hash:ip family inet timeout 5400
create ssh_loggedon6 hash:ip family inet6 timeout 5400
create ssh_dynblock4 hash:ip family inet timeout 1800
create ssh_dynblock6 hash:ip family inet6 timeout 1800
Regresaremos a estos conjuntos en particular a continuación y la justificación de por qué están configurados como están.
Si desea establecer su tiempo de espera para una dirección IP en particular, simplemente puede decir:
add ssh_dynblock4 1.2.3.4 timeout 7200
Para bloquear IP 1.2.3.4 durante dos horas en lugar de la media hora predeterminada (establecida).
Si tuviera que ver eso con ipset save ssh_dynblock4
un poco de tiempo, vería algo en la línea de:
create ssh_dynblock4 hash:ip family inet hashsize 1024 maxelem 65536 timeout 1800
add ssh_dynblock4 1.2.3.4 timeout 6954
Advertencias de tiempo de espera
- los tiempos de espera son una característica en cualquier conjunto dado. Si el conjunto no se creó con soporte de tiempo de espera, recibirá un error (por ejemplo
Kernel error received: Unknown error -1
).
- los tiempos de espera se dan en segundos. Utilice las expresiones aritméticas de Bash para pasar de minutos a segundos, por ejemplo. P.ej:
sudo ipset add ssh_dynblock4 1.2.3.4 timeout $((120*60))
Comprobando si existe una entrada en un conjunto de IP dado
Dentro de sus scripts puede ser útil ver si ya existe una entrada. Esto se puede lograr con lo ipset test
que devuelve cero si la entrada existe y, de lo contrario, no es cero. Por lo tanto, las comprobaciones habituales se pueden aplicar en un script:
if ipset test dns4 8.8.8.8; then
echo "Google DNS is in the set"
fi
Sin embargo, en muchos casos es preferible usar el -exist
interruptor para ipset
que no se queje de las entradas existentes.
Completar conjuntos de IP a partir de iptables
reglas
Esta, en mi opinión, es una de las características principales de los conjuntos de IP. No solo puede hacer coincidir las entradas de un conjunto de IP, sino que también puede agregar nuevas entradas a un conjunto de IP existente.
Por ejemplo, en esta respuesta a esta pregunta tienes:
-A INPUT -p tcp -i eth0 -m state --state NEW --dport 22 -m recent --update --seconds 15 -j DROP
-A INPUT -p tcp -i eth0 -m state --state NEW --dport 22 -m recent --set -j ACCEPT
... con la intención de limitar la velocidad de los intentos de conexión a SSH (puerto TCP 22). El módulo utilizado recent
realiza un seguimiento de los intentos de conexión recientes. Sin embargo, en lugar del state
módulo, prefiero el conntrack
módulo.
# Say on your input chain of the filter table you have
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
# Then inside the SSH chain you can
# 1. create an entry in the recent list on new connections
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
# 2. check whether 3 connection attempts were made within 2 minutes
# and if so add or update an entry in the ssh_dynblock4 IP set
-4 -A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock4 src --exist
-6 -A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock6 src --exist
# 3. last but not least reject the packets if the source IP is in our
# IP set
-4 -A SSH -m set --match-set ssh_dynblock4 src -j REJECT
-6 -A SSH -m set --match-set ssh_dynblock6 src -j REJECT
En este caso, estoy redirigiendo el flujo a la SSH
cadena de modo que no tenga que repetirme -p tcp --dport ssh
para cada regla.
Reiterar:
-m set
se da iptables
cuenta de que estamos usando conmutadores del set
módulo (que maneja conjuntos de IP)
--match-set ssh_dynblock4 src
le dice iptables
que haga coincidir la dirección de origen ( src
) con el conjunto nombrado ( ssh_dynblock4
)
- esto corresponde a
sudo ipset test ssh_dynblock4 $IP
(donde $IP
contiene la dirección IP de origen para el paquete)
-j SET --add-set ssh_dynblock4 src --exist
agrega o actualiza la dirección de origen ( src
) del paquete en el conjunto de IP ssh_dynblock4
. Si existe una entrada ( --exist
), simplemente se actualizará.
- esto corresponde a
sudo ipset -exist add ssh_dynblock4 $IP
(donde $IP
contiene la dirección IP de origen para el paquete)
Si en cambio quisieras hacer coincidir la dirección de destino / destino, usarías en dst
lugar de src
. Consulte el manual para más opciones.
Conjuntos de conjuntos
Los conjuntos de IP pueden contener otros conjuntos. Ahora, si siguió el artículo hasta aquí, se habrá preguntado si es posible combinar conjuntos. Y por supuesto que es. Para los conjuntos de IP de arriba, podemos crear dos conjuntos de conjuntos ssh_dynblock
y, ssh_loggedon
respectivamente, contener los conjuntos solo IPv4 y solo IPv6:
create ssh_loggedon4 hash:ip family inet timeout 5400
create ssh_loggedon6 hash:ip family inet6 timeout 5400
create ssh_dynblock4 hash:ip family inet timeout 1800
create ssh_dynblock6 hash:ip family inet6 timeout 1800
# Sets of sets
create ssh_loggedon list:set
create ssh_dynblock list:set
# Populate the sets of sets
add ssh_loggedon ssh_loggedon4
add ssh_loggedon ssh_loggedon6
add ssh_dynblock ssh_dynblock4
add ssh_dynblock ssh_dynblock6
Y la siguiente pregunta que debería surgir en su mente es si esto nos permite hacer coincidir y manipular conjuntos de IP de una manera independiente de la versión de IP.
Y la respuesta a eso es un rotundo: ¡SÍ! (por desgracia, esto no se documentó explícitamente la última vez que lo verifiqué)
En consecuencia, las reglas de la sección anterior se pueden reescribir para leer:
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
-A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock src --exist
-A SSH -m set --match-set ssh_dynblock src -j REJECT
lo cual es mucho más conciso. Y sí, esto está probado y probado y funciona de maravilla.
Poniendo todo junto: defensa de fuerza bruta SSH
En mis servidores, tengo un script ejecutado como un cron
trabajo que toma un montón de nombres de host y los resuelve en direcciones IP, luego lo introduce en el conjunto de IP para "hosts de confianza". La idea es que los hosts de confianza tengan más intentos de iniciar sesión en el servidor y no estén necesariamente bloqueados por el tiempo que nadie más.
Por el contrario, tengo países enteros bloqueados para conectarse a mi servidor SSH, con la excepción (potencial) de hosts confiables (es decir, el orden de las reglas es importante).
Sin embargo, eso se deja como un ejercicio para el lector. Aquí me gustaría agregar una solución ordenada que utilizará los conjuntos contenidos en el ssh_loggedon
conjunto para permitir que los intentos de conexión posteriores se transmitan y no estén sujetos al mismo escrutinio que los otros paquetes.
Es importante recordar los tiempos de espera predeterminados de 90 minutos ssh_loggedon
y 30 minutos ssh_dynblock
al observar las siguientes iptables
reglas:
-A INPUT -i eth+ -p tcp --dport ssh -j SSH
-A SSH -m set --match-set ssh_loggedon src -j ACCEPT
-A SSH -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A SSH -m conntrack --ctstate NEW -m recent --set --name tarpit
-A SSH -m conntrack --ctstate NEW -m recent --rcheck --seconds 120 --hitcount 3 --name tarpit -j SET --add-set ssh_dynblock src --exist
-A SSH -m set --match-set ssh_dynblock src -j REJECT
Ahora debería preguntarse cómo termina la dirección IP de conexión en los ssh_loggedon
subconjuntos. Entonces sigue leyendo ...
Bonificación: agregar la IP con la que inicia sesión durante el inicio de sesión SSH
Si ha experimentado con sshrc
y amigos, se habrá enterado de sus defectos. Pero PAM viene al rescate. Un módulo llamado pam_exec.so
nos permite invocar un script durante el inicio de sesión SSH en un punto en el que sabemos que el usuario está admitido.
A /etc/pam.d/sshd
continuación, las entradas pam_env
y pam_selinux
agregan la siguiente línea:
session optional pam_exec.so stdout /path/to/your/script
y asegúrese de que su versión del script ( /path/to/your/script
arriba) exista y sea ejecutable.
PAM usa variables de entorno para comunicar lo que está sucediendo, por lo que puede usar un script simple como este:
#!/bin/bash
# When called via pam_exec.so ...
SETNAME=ssh_loggedon
if [[ "$PAM_TYPE" == "open_session" ]] && [[ -n "$PAM_RHOST" ]]; then
[[ "x$PAM_RHOST" != "x${PAM_RHOST//:/}" ]] && SETNAME="${SETNAME}6" || SETNAME="${SETNAME}4"
ipset -exist add $SETNAME "$PAM_RHOST"
fi
Desafortunadamente, la ipset
utilidad no parece tener la inteligencia incorporada de netfilter. Por lo tanto, debemos distinguir entre IPv4 e IPv6 IP al agregar nuestra entrada. De ipset
lo contrario , supondremos que queremos agregar otro conjunto al conjunto de conjuntos, en lugar de la IP. Y, por supuesto, es poco probable que haya un conjunto con el nombre de una IP :)
Por lo tanto, verificamos :
la dirección IP y agregamos 6
el nombre del conjunto en tal caso y de 4
otra manera.
El fin.