Port knocking o golpeteo de puertos es un método que proporciona seguridad frente ataques de servicios de red, consiste en realizar una serie de peticiones de conexión con una secuencia ordenada de puertos para poder habilitar una regla previamente configurada en el máquina remota que nos permita abrir una comunicación única entre cliente/servidor y establecer finalmente una conexión abierta SSH hacia el servidor remoto. El uso de esta implementación puede ser catalogado como seguridad por oscuridad.
¿Cómo funciona una conexión SSH usando Port knocking?
- Tenemos un servidor SSH que expone el puerto por defecto 22 o bien otro un puerto específico como por ejemplo 4422 (será el puerto que se trate en este escenario).
- Se añade una regla a nivel de firewall del propio servidor para rechazar todas conexiones entrantes sobre el puerto SSH previamente configurado 4422.
- Para poder conectarnos a este servidor usando una técnica de port kockning de hasta tres "golpeteos" de puertos para establecer una conexión SSH, necesitamos realizar una petición a estos puertos, en este escenario mostraré tres formas de hacerlo (knockd, telnet y nmap).
- Una vez realizado el knocking de puertos en el orden establecido (que veremos posteriormente en el fichero de configuración) este disparará la acción de ejecutar un comando, este comando no será más que establecer una nueva regla en este caso vía iptables para permitir la conexión SSH únicamente desde la IP origen que realizó la secuencia de golpeteo de puertos y no desde ninguna otra IP alcanzable de la misma red.
- Finalmente para volver aplicar la regla de bloqueo y restablecer esta configuración al terminar nuestra sesión, se lanzará el golpeteo de puertos de salida de SSH. De modo que la configuración vuelva a restablecerse y vuelva a cerrarse el puerto hacia el exterior.
Configurando Iptables
Bloquear puerto SSH configurado vía Iptables
En este escenario el servidor SSH se ejecuta en un entorno Debian y expone el puerto 4422 (en vez del puerto 22 por defecto).
Añadimos un regla iptables para rechazar todas las conexiones entrantes hacia el puerto 4422 del servidor SSH configurado.
sudo iptables -A INPUT -p tcp --dport 4422 -j REJECT --reject-with tcp-reset
Iptables: diferencias entre DROP y REJECT (estado de puertos "filtered" o "closed")
La teoría nos dice que uso de DROP no hará nada con el paquete recibido, simplemente lo descartará sin enviar ningún tipo de respuesta. Al contrario de REJECT donde se genera un paquete de respuesta rechazando la conexión, si no se especifica el tipo de respuesta por defecto este paquete sería un "ICMP Destination port unreachable".
En la práctica esto no es del todo cierto. Cuando la regla es DROP es cierto que el paquete se descarta y no se envía ningún paquete de respuesta, sin embargo una regla DROP anunciará que existe un control de puertos y los escáneres sabrán que se está haciendo uso de un firewall devolviendo un estado de puerto "filtered". Ocurre lo mismo si solo usamos REJECT sin especificar un tipo concreto de respuesta, este responderá con un paquete "ICMP Destination port unreachable" anunciando así el filtrado de dicho puerto.
Si lo que queremos es ocultar el puerto y devolver un estado "closed" como resultado en un escaneo de puertos, haciendo creer que el puerto o servicio que se ejecuta en el lado servidor no se está usando o simplemente no existe, debemos especificar un tipo de respuesta en una regla REJECT.
Cuando usamos TCP o UDP sobre un puerto la respuesta debe responder con los flags RST/ACK, de este modo estaremos indicando a los escáneres un estado de puerto "closed".
- TCP: iptables -A INPUT -p tcp --dport 4422 -j REJECT --reject-with tcp-reset
- UDP: iptables -A INPUT -p udp --dport 4422 -j REJECT --reject-with icmp-port-unreachable
Capturando este tipo de tráfico de red con un sniffer como
Wireshark, en la siguiente captura de pantalla se muestran todas las combinaciones posibles para este caso donde se puede ver el tipo de paquete de respuesta que genera según el tipo de regla aplicada en el lado servidor.
Reglas DROP y REJECT con estado "FILTERED" (TCP/UDP)
iptables -A INPUT -p tcp --dport 4422 -j DROP
|
Figura 1: Estado de puerto "filtered" usando DROP en TCP. |
iptables -A INPUT -p udp --dport 4422 -j DROP
|
Figura 2: Estado de puerto "open|filtered" usando DROP en UDP. |
iptables -A INPUT -p tcp --dport 4422 -j REJECT
|
Figura 3: Estado de puerto "filtered" usando REJECT en TCP. |
Reglas DROP y REJECT con estado "CLOSED" (TCP/UDP)
iptables -A INPUT -p tcp --dport 4422 -j REJECT --reject-with tcp-reset
|
Figura 4: Estado de puerto "closed" usando REJECT en TCP. |
iptables -A INPUT -p udp --dport 4422 -j REJECT --reject-with icmp-port-unreachable
o
iptables -A INPUT -p udp --dport 4422 -j REJECT
|
Figura 5: Estado de puerto "closed" usando REJECT en UDP. |
Guardado y persistencia de reglas Iptables después de reiniciar la máquina
Instalamos el paquete iptables-persistent.
sudo apt-get install iptables-persistent
Guardamos y cargamos reglas configuradas de Iptables.
sudo netfilter-persistent save
sudo netfilter-persistent reload
Para proporcionar la persistencia de las reglas configuradas de Iptables después un reinicio de la máquina. En el fichero /etc/crontab añadimos la ejecución para cargar las reglas guardas después cada arranque del sistema.
sudo echo "@reboot sudo netfilter-persistent reload &" >> /etc/crontab
Implementación de Port Knocking - knockd
Instalamos el servicio de knockd
sudo apt install -y knockd
Configuración de knockd
Editamos el fichero de configuración de /etc/knockd.conf.
[options]
#UseSyslog
logfile = /var/log/knockd.log
[openSSH]
sequence = 7500,8200,9800
seq_timeout = 5
command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 4422 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 9300,4500,6200
seq_timeout = 5
command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 4422 -j ACCEPT
tcpflags = syn
Por cuestiones de monitorización y depuración de posibles errores establecemos un logfile específico para este servicio con el path donde se almacenarán los logs de knockd. Comentamos UseSyslog ya que si mantenemos esto por defecto los logs se almacenarán en el fichero de sistema /var/log/syslog.
Disponemos de dos principales directivas. Una para abrir la conexión SSH (openSSH) y otra para cerrar y restablecer la configuración de la conexión SSH (closeSSH).
Sección openSSH:
- sequence: Se define la configuración secreta de puertos a golpear. Se puede especificar tres o más puertos y su vez indicar el tipo de protocolo, bien sea por tcp o udp. Por defecto se estable a tres puertos en protocolo tcp. En este caso modifiqué los puertos y secuencia en el siguiente orden: 7500,8200,9800.
- seq_timeout: Tiempo de esperará entre la ejecución de un golpeteo a otro de puertos. Por defecto se establece a 5 segundos, aunque podemos aumentarlo si no fuese suficiente. No se recomienda establecer grandes intervalos de tiempo para evitar ataques de fuerza bruta que realizan esta acción de forma automatizada intentando encontrar la combinación correcta de puertos. Una herramienta utilizada podría ser KnockIt - https://github.com/eliemoutran/KnockIt.
- command: Comando que el servidor ejecutará cuando detecte la combinación correcta de puertos previamente golpeados. En este caso se agregará en una nueva regla (iptables -I) en el primer orden de la jerarquía de prioridad de la tabla INPUT de iptables para permitir únicamente desde la dirección IP origen la la conexión hacia el puerto 4422 configurado para este servidor SSH.
- tcpflags: Tipo de paquetes que reconocerá el servidor como válidos para el port knocking (en este caso con un flag syn).
Sección closeSSH:
Los parámetros son los mismos que las anteriores con la diferencia de indicar otro número de secuencia distinta (en este caso: 9300,4500,6200) para ejecutar otra regla en el parámetro de command.
- command: Se eliminará (iptables -D) la regla añadida anteriormente. De modo que el servidor vuelva a "restablecer" con la configuración inicial rechazando todas las solicitudes origen hacia el puerto 4422 de SSH.
Habilitar el daemon knockd
Editamos el fichero /etc/default/knockd. Establecemos START_KNOCKD a valor "1" para habilitar el servicio y en KNOCKD_OPTS indicamos la interfaz de red que que usará el servicio, en este caso enp0s3.
START_KNOCKD=1
KNOCKD_OPTS="-i enp0s3"
Iniciamos el servicio knockd y habilitamos un tipo de inicio automático del servicio en el arranque del sistema.
sudo systemctl start knockd
sudo systemctl enable knockd
¿Cómo conectarse a un servidor SSH usando Port Knocking?
Analizando el escenario actual
- Máquina Debian - Cliente: 10.0.0.23
- Máquina Debian - Servidor: 10.0.0.22
En la siguiente captura se muestra un intento de conexión SSH desde la máquina cliente (10.0.0.23) sin el uso de Port knocking y vemos como la conexión ha sido rechazada.
Si comprobamos el estado de puerto con
Nmap vemos que el puerto se muestra en un estado cerrado "closed". Esto es así porque previamente habíamos especificado el tipo de respuesta en REJECT con un
tcp-reset el cual nos devuelve un paquete RST/ACK (ver figura 1 para más detalles).
Si revisamos la configuración de reglas de iptables en la máquina servidor (10.0.0.22) vemos como hay una regla que rechaza cualquier tipo de conexión por el puerto 4422 desde cualquier IP origen.
|
Figura 6: Intento de conexión SSH usando knockd sin el uso de secuencia Port Knocking. |
Opción 1: usando knock con Port Knocking
Desde la máquina cliente instalamos knockd. Para conectarnos al servidor remoto ejecutamos lo siguiente.
$ knock -v 10.0.0.22 7500 8200 9800
hitting tcp 10.0.0.22:7500
hitting tcp 10.0.0.22:8200
hitting tcp 10.0.0.22:9800
Una vez golpeado los puertos el orden de secuencia de correcto, ejecutamos ssh para conectarnos al servidor por el puerto configurado 4422.
ssh -p 4422 user@10.0.0.22
En la siguiente captura de pantalla podemos ver como después realizar el intento de conexión SSH al servidor remoto usando la combinación correcta de golpeteo de puertos este ejecuta el comando establecido en la directiva "openSSH" configurado en el fichero /etc/knockd.conf donde se indica que se añada en primer orden una nueva regla iptables para permitir la comunicación únicamente a la IP origen de la máquina cliente permitiendo así la conexión SSH hacia la máquina servidor remota.
Como prueba podemos comprobar el estado de conexión de puertos con Nmap, desde la máquina cliente permitida vemos como el puerto 4422 está en estado abierto "open", sin embargo si comprobamos lo mismo desde otra máquina de la red con otra IP origen distinta vemos como para esa otra máquina el estado del puerto 4422 es "closed". Por lo tanto podemos decir que las reglas están funcionando correctamente y estamos "ocultando" o más bien filtrando el uso de las comunicaciones en un puerto concreto entre otras máquinas, la máquina cliente origen y la máquina servidor.
Si revisamos la máquina servidor vemos como ahora existe una nueva regla de iptables en el orden de prioridad 1 (será la primera regla que interprete el firewall) permitiendo la conexión al puerto 4422 desde la IP origen 10.0.0.23 (máquina cliente). Si analizamos los logs /var/log/knockd.log también verificamos que se ejecutó correctamente el orden de secuencia de golpeteo de puertos y el comando que agrega esta regla al firewall local de iptables.
Finalmente salimos de la sesión SSH remota, para volver restablecer las reglas iptables de la máquina servidor y así volver rechazar las conexiones por el puerto 4422 a todas las máquinas e incluida la máquina cliente. Ejecutamos la secuencia de golpeteo de puertos establecida en la directiva de "closeSSH". De esta forma si volvemos a revisar las reglas en la máquina servidor vemos como esta se elimina y se vuelve establecer con prioridad 1 la regla de bloqueo que rechaza todas las conexiones desde cualquier IP origen.
|
Figura 7: Intento de conexión SSH usando knockd con la secuencia correcta de Port Knocking. |
Opción 2: usando telnet con Port Knocking
Otra forma de realizar port knocking es hacer uso del comando telnet. Recibirá un mensaje de "Connection refused", pero está bien, ya que telnet está deshabilitado en ese puerto y solo queremos enviar un paquete con flag "TCP SYN".
$ telnet 10.0.0.22 7500
Trying 10.0.0.22...
telnet: Unable to connect to remote host: Connection refused
$ telnet 10.0.0.22 8200
Trying 10.0.0.22...
telnet: Unable to connect to remote host: Connection refused
$ telnet 10.0.0.22 9800
Trying 10.0.0.22...
telnet: Unable to connect to remote host: Connection refused
Una vez se concluya el comando telnet durante las tres secuencias de puertos, la conexión SSH por el puerto 4422 se abrirá para la máquina cliente.
|
Figura 8: Intento de conexión SSH usando telnet con la secuencia correcta de Port Knocking. |
Opción 3: usando Nmap con Port Knocking
Otra forma de realizar port knocking es hacer uso de la herramienta de escaneo de puertos Nmap. Con el parámetro -sS podemos enviar hacia el servidor paquetes con flag "TCP SYN". Concatenando la secuencia de comandos correcta podemos lanzar este tipo de paquetes ejecutando así la regla que nos permite establecer conexión con el servidor SSH remoto.
sudo nmap -sS -p7500 10.0.0.22 ; nmap -sS -p8200 10.0.0.22 ; nmap -sS -p9800 10.0.0.22
|
Figura 9: Intento de conexión SSH usando Nmap con la secuencia correcta de Port Knocking. |
Conclusiones
Añadir este mecanismo como método de autenticación SSH en servidores es una buena medida de seguridad por oscuridad. Si bien este proceso es divertido de implementar y verlo en acción, es posible que no sea factible implementarlo en entornos de producción donde hay una gran cantidad de servidores SSH o si se necesita automatizar accesos SSH.
Considero que es una buena medida de seguridad a implementar solamente para aquellos servidores realmente críticos y que tengan una cierta exposición externa en los que dado su diseño de arquitectura subyacente de red no permita ser protegido en capas anteriores con un firewall o DMZ. Incluso podríamos añadir una segunda capa de protección como sería la implementación de un
segundo factor de autenticación 2FA en conexiones SSH.