Publi

Knock, knock, knockin’ on server’s ports – Port knocking con ejemplos


¡Tenía muchas ganas de escribir este post! De hecho, hay muchas personas a las que les he hablado de la técnica port knocking y me he tenido que poner a buscar un ejemplo que me convenciera por Internet. ¡Ya no tengo excusa! Y, como siempre, intentaré incluir varios ejemplos para copiar y pegar sin miedo explicados para no perder detalle.

¿Qué es el port knocking?

Imaginad que en mi servidor tengo activo un servicio privado, al que no quiero que nadie acceda, sólo yo. Por ejemplo, puede ser un sistema de administración remota, monitorización, logs, rescate, o simplemente cosas que no quiero que sean públicas. Y, claro, cuanto más tiempo esté un servicio expuesto a Internet, más probabilidades de ser vulnerado.
Por ejemplo, si tenemos un servidor SSH corriendo, normalmente veremos el puerto 22 abierto, y cualquiera que nos haga un scan de puertos podrá verlo (¿quién va a querer hacerme un scan de puertos?):

nmap servidor.com
Starting Nmap 7.01 ( https://servidor.com ) at 2017-08-01 00:24 CEST
Nmap scan report for totaki.com (123.231.32.132)
Host is up (0.055s latency).
rDNS record for 123.231.32.132: servidor.com
Not shown: 997 closed ports
PORT    STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
443/tcp  open  https

Está bien, una de las formas de ocultar un servidor SSH es cambiar el puerto, y entonces un análisis rápido no lo verá. De todas formas es un puerto abierto y, tarde o temprano, esas cosas salen a la luz. Así que si abres tu puerto 753 como servidor SSH, cualquiera se podrá conectar a él y basta hacer un netcat al puerto y ver cómo se presenta:

nc -t servidor_oculto.com 753
SSH-2.0-OpenSSH_6.7p1

¡Listo! Sabemos que es un SSH, vamos a atacar, intentando identificarnos como los usuarios típicos: root, admin y demás, ¡Y conocemos la versión! Con lo malo que es eso para mantener un sistema medianamente seguro. Aunque bueno, aquí tienes una guía para endurecer un servidor SSH y hacerlo más seguro. No obstante, también podríamos tener un servicio corriendo que fuera algo más inseguro con una autentificación algo más floja.

Así que vamos a hacer un sistema de cerradura, como los de las cajas fuertes de las películas, en los que tenemos que girar la rueda varias veces siguiendo una combinación hasta que la podemos abrir o como cuando llamamos a la puerta con una combinación de golpes y esperas perfecta para que sepan que somos nosotros. De hecho port knocking lo podemos traducir como golpeo de puertos, es decir, intentaremos conectar con varios puertos y, el firewall de nuestro servidor mágicamente nos reconocerá y nos abrirá el puerto sólo para nosotros y así poder conectarnos.

Entonces, imaginemos que mi combinación es 1111, 3333, 2222 (lo pongo así para que veamos que no es necesario que los números de puerto estén ordenados), para abrir el puerto 22 de SSH necesitaré hacer intentos de conexión a los puertos 1111, 3333 y 2222. Estos puertos están cerrados, por lo que no conectarán, y el servidor parece que pasa de nosotros porque no hace nada, pero, tras los intentos de acceso a dichos puertos, podremos establecer una conexión con el puerto 22.

¿Podemos considerarnos paranoicos con este sistema de seguridad? Puede, y a muchos sysadmins les encanta. Al menos nos proporciona ocultación de los servicios críticos, así como de sus protocolos. Aunque, como todo, tiene sus riesgos.

No estoy inventando nada


Antes de continuar, tengo que decir que esta técnica es muy antigua. En junio de 2003, Martin Krzywinski publicó en Sys Admin Magazine un artículo bautizando de paso a esta técnica con un nombre chulo. Pero claro, eso no quiere decir que no se hubiera hecho antes.

Beneficios de esta técnica

Incluyo algunos puntos (no todos) que vienen en el artículo original de Krzywinski:

  • Lo principal es que es un método de autentificación silencioso. Como dije antes, el sistema parece que pasa de ti. Además, los puertos a los que accederemos están previamente cerrados, por lo que con un escaneo de puertos no sabremos los puertos que hay que golpear, y mucho menos el orden.
  • Si encima disponemos de un sistema que detecte y bloquee los escaneos de puertos, podríamos evitar que si nuestra clave de golpeo es de tres puertos, pudieran acceder al sistema tras hacer 3 escaneos. Además de que podríamos configurar el tiempo entre golpeos por lo que si tardas mucho en golpear tienes que empezar de nuevo. Aunque algunas implementaciones modernas de esta técnica pueden también evitar que un escaneo cuente como knock (hará knock cuando llegue al puerto deseado pero invalidará el knock cuando pruebe el siguiente puerto).
  • No tenemos que cambiar nada en el software servidor que escucha en el puerto que vamos a abrir. Es más, esta configuración es totalmente independiente.
  • Elimina entradas en los logs de sistema. ¡Cuántas líneas logea el demonio SSH con intentos de acceso! Con este sistema, la mayoría de intentos de acceso a un servidor SSH no llegarán a afectarnos lo más mínimo. El puerto del servicio está cerrado así que el propio firewall se deshará de todos.

Desventajas e inconvenientes

  • No es conveniente proteger puertos como el 80 (http) o el 25 (smtp), porque los clientes no se van a poner a hacer el proceso de knocking cada vez que quieran algo de esos servidores. Esto está pensado para servicios que deben ser privados.
  • Si tenemos servicios abiertos al público en el mismo servidor. Alguien podría utilizar un problema, bug, puerta trasera, etc. para entrar en el servidor y podría echar por tierra la seguridad que queremos tener con los servicios privados. Esto no es específicamente de la técnica, es un problema general de todos los sysadmins.
  • Tenemos que tener cuidado con los servicios de automatización del firewall. Yo utilizaré iptables, así que si utilizamos UFW o APF, puede que éstos no se lleven bien y tendremos que trabajar un poco más para que todo funcione en sintonía.
  • Es algo más que debemos mantener. Tanto si lo hacemos con scripts de iptables como si lo hacemos con un demonio. Como administradores del sistema tendremos que asegurarnos de que el sistema está vivo y no falla. Sólo imagina que si el sistema que controla el port knocking falla, nos quedaremos sin acceso al servicio que hemos ocultado.
  • Tenemos una contraseña para abrir el puerto. Podríamos hacer un script que haga el knocking automáticamente, y ese script nos lo querremos llevar con nosotros a cualquier lado. Y ya estamos liberando información privada si lo subimos a un servidor o lo anotamos, etc.
  • Si muchos usuarios tienen la clave del knocking e intentan acceder al servidor, la información temporal que tiene que almacenar el firewall puede crecer mucho. No es preocupante si es un gran servidor, pero la memoria se evapora.
  • Este método permite la entrada a la IP que haga el juego completo de knocks. Es decir, si estamos haciéndolo a una máquina en Internet y varias personas compartimos conexión cualquiera en nuestra red local podría entrar. Eso también podría ser una ventaja… según se mire.

Ejemplo con iptables

Entonces, lo que queremos es que nuestro servidor tenga el puerto 22 (ssh) cerrado (cojo el 22, pero podría ser cualquier otro). Y cuando intente conectar con los puertos 1111, 3333 y 2222, puertos que están cerrados y los intentos de conexión no harán nada, veremos el puerto 22 abierto para poder conectar por ssh. Así que vamos a definir los diferentes estados por los que pasará mi sistema:

Estado Descripción
Inicial Configuración básica del firewall
WAITING Esperando golpeos. Desde este estado repartimos los paquetes entrantes a los demás estados
KNOCK1 Entra el primer toque
KNOCK2 Entra el segundo toque
KNOCK3 Entra el tercer toque
ACCESSGRANTED Esperando conexión con el puerto abierto

También vamos a suponer que tenemos un servidor web instalado, por lo que el puerto 80 deberá estar abierto (en la práctica deberíamos también abrir el 443). Si tienes otro firewall o un script de firewall, tal vez esto tengas que solucionarlo ahí.

Crearemos los estados en iptables:

iptables -N WAITING
iptables -N KNOCK1
iptables -N KNOCK2
iptables -N KNOCK3
iptables -N ACCESSGRANTED

Esto es lo que vamos a hacer lo siguiente en el servidor.

¿Qué haremos con el tráfico normal del servidor?

Asumo que el servidor SSH ya está instalado y funcionando.
Es decir, vamos a definir las directivas del tráfico que aceptará nuestro servidor (lo que empieza por # se ejecutará como root directamente. Si no somos root, tal vez podríamos usar sudo).

Lo primero que hacemos es aceptar todo el tráfico entrante y saliente (INPUT y OUTPUT, algunos también necesitaréis activar FORWARD), seguidamente con -F limpiamos las reglas de iptables hasta el momento (cuidado si tienes firewalls ya).

iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F

La siguiente línea hace que las conexiones ya establecidas sigan su curso. Es decir, tanto las conexiones al servidor web como las conexiones que ya tenemos establecidas en el puerto 22, que no tengan ningún problema para continuar.

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Lo siguiente es aceptar todo lo que venga de localhost. Normalmente podemos tener varios servicios en la misma máquina que conectan con ellos mismos, y son todos privados, por lo que éstos no tienen que tener impedimentos.

iptables -A INPUT -i lo -j ACCEPT

Con estas líneas, rechazamos los paquetes ICMP, por ejemplo el PING. Esto no es esencial, pero muchos lo hacen, y desde aquí quiero demostrar que podemos hacer que ciertas conexiones continúen o al contrario, que se detengan.
Como hacemos en la siguiente línea, que es aceptar todo lo que provenga del puerto 80 tcp. Es decir, nuestro servidor web.
# iptables -A INPUT -p icmp -j DROP
# iptables -A INPUT -p tcp –dport 80 -j ACCEPT
[/simterm]

Todas las conexiones que hayan llegado hasta aquí, las vamos a pasar al estado WAITING

iptables -A INPUT -j WAITING

Configuramos los estados knockX y AccessGranted

Estos estados “knock” también pueden ser considerados puertas, como si un usuario tuviera que pasarlas todas para ver la libertad. Éstos serán KNOCK1, KNOCK2, KNOCK3. Empezamos por el primer intento. Estamos esperando un knock al puerto tcp 1111, conexión que tenemos que tirar porque ahí no hay servicio y no queremos que el puerto figure como cerrado. Eso sí, vamos a ponerle un flag “PASSED1” para que cuando entre el siguiente paquete sepa que tiene que este paso ya está hecho y entre directamente en KNOCK2.
La primera línea sólo nos sirve para enviar un log (que podemos ver con dmesg. El ejemplo de dmesg está al final del post).
En la tercera línea me encargo de tirar todos los demás paquetes que vengan. Sin flag ni nada, esos no han pasado la prueba.

En este post hablo de poner un flag a la IP, para indicar que se ha pasado la prueba. En realidad, iptables lo que hace es meter una IP en una lista. Al final estamos hablando de lo mismo, pero de forma más eficiente. En lugar de crear un texto que diga PASSED1 asociado a una IP, lo que hacemos es crear una lista llamada PASSED1 y meter ahí la IP, el momento en el que entró el último paquete y alguna información más. Si queremos, podemos ver el contenido de las listas haciendo:

cat /proc/net/xt_recent/PASSED1
src=12.124.229.34 ttl: 51 last_seen: 4306420264 oldest_pkt: 1 4306420264
src=21.123.219.55 ttl: 51 last_seen: 4306420224 oldest_pkt: 1 4306420224
iptables -A KNOCK1 -j LOG --log-level 4 --log-prefix “Knock 1: ”
iptables -A KNOCK1 -p tcp --dport 1111 -m recent --name PASSED1 --set -j DROP
iptables -A KNOCK1 -j DROP

Vamos, al KNOCK2, al que entraremos cuando recibamos el segundo knock. Éste será parecido al primero, sólo que antes de nada tendremos que borrar el flag PASSED1. Esto lo hacemos para que cuando un usuario haga mal el golpeo, es decir, después del 1111, golpee el 5555 y luego el 3333 ya no cuente, así si hacen varios escaneos que no bloqueamos el usuario no entrará. Por lo demás, hacemos que cuando se acceda al puerto 3333 pongamos la etiqueta “PASSED2” para que, como antes, cuando llegue un paquete nuevo al estado WAITING, éste sepa que tendrá que ir directamente al estado KNOCK3 porque KNOCK2 ya lo ha pasado y además, el paquete lo tiramos, porque en el puerto 3333 no hay nada. Por lo demás, saltaremos al estado KNOCK1 para ver si la conexión pasa el primer toque.

iptables -A KNOCK2 -m recent --name PASSED1 --remove
iptables -A KNOCK2 -j LOG --log-level 4 --log-prefix “Knock 2: ”
iptables -A KNOCK2 -p tcp --dport 3333 -m recent --name PASSED2 --set -j DROP
iptables -A KNOCK2 -j KNOCK1

Este estado sí que es mucho más parecido al anterior y, con esto, ya nos da una pista para que nuestro sistema pueda tener tres, cuatro o cinco knocks. Cuantos más knocks, más difícil será entrar, será un modo más paranoico, y nuestro firewall será más complicado (recordad que esto requerirá un mantenimiento y, si hay muchos usuarios que quieren entrar, todo será más complicado). Lo único que cambian son los números. Es decir, en el estado KNOCK3 entramos porque el flag PASSED2 está activo (es decir, hemos superado el paso2 y por eso estamos en knock3), luego logeamos diciendo que estamos en knock3 y si alguien intenta conectar por el puerto 2222, colocaremos el flag PASSED3. Sencillo, ¿no?

iptables -A KNOCK3 -m recent --name PASSED2 --remove
iptables -A KNOCK3 -j LOG --log-level 4 --log-prefix “Knock 3: ”
iptables -A KNOCK3 -p tcp --dport 2222 -m recent --name PASSED3 --set -j DROP
iptables -A KNOCK3 -j KNOCK1

Y… una vez superados los tres knocks, es decir, la conexión tiene el flag PASSED3, el acceso ya está permitido y ahora aceptaremos las conexiones al puerto 22.

iptables -A ACCESSGRANTED -m recent --name PASSED3 --remove
iptables -A ACCESSGRANTED -j LOG --log-level 4 --log-prefix “Access Granted: ”
iptables -A ACCESSGRANTED -p tcp --dport 22 -j ACCEPT
iptables -A ACCESSGRANTED -j KNOCK1

Colocando cada paquete en su estado

Una vez que tenemos los estados KNOCKX y ACCESSGRANTED configurados, necesitamos que el estado WAITING, que será al estado que saltemos cuando llega un nuevo paquete (y éste ha sido previamente filtrado), reparta los paquetes que lleguen entre los diferentes estados en función de los flags que hemos ido definiendo. Es decir:

  • Si no tenemos flags, saltamos a KNOCK1, porque tal vez sea una conexión con el puerto 1111, si no lo es, no haremos nada.
  • Si la IP tiene el flag PASSED1, saltaremos a KNOCK2 y esperaremos el segundo toque.
  • Si la IP tiene el flag PASSED2, saltaremos a KNOCK3 y esperaremos el tercer toque.
  • Si la IP tiene el flag PASSED3, saltaremos a ACCESSGRANTED y esperaremos una conexión por el puerto 22.

Además, desde aquí podemos asignar tiempos. Es decir, si la IP tiene el flag PASSED1 y además han pasado menos de 10 segundos desde que entró en la lista, entramos a KNOCK2. Con esto limitamos el tiempo que tiene el usuario para poder completar cada paso y entrar en el sistema.

iptables -A WAITING -m recent --rcheck --seconds 30 --name PASSED3 -j ACCESSGRANTED
iptables -A WAITING -m recent --rcheck --seconds 10 --name PASSED2 -j KNOCK3
iptables -A WAITING -m recent --rcheck --seconds 10 --name PASSED1 -j KNOCK2

Todo el tráfico restante, lo mandamos a KNOCK1

iptables -A WAITING -j KNOCK1

El script completo

A mí me encanta poder copiar y pegar de las webs, al menos para probar las cosas de un tirón. Así que, os pongo el script completo aquí.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash

# Creamos objetivos de iptables
iptables -N WAITING
iptables -N KNOCK1
iptables -N KNOCK2
iptables -N KNOCK3
iptables -N ACCESSGRANTED

# Establecemos una política básica de permisos
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
# Ignoramos ping, bueno, paquetes ICMP en general. Sólo para probar, mejor será que se lo dejemos a nuestro firewall que tendrá mejores reglas para esto.
iptables -A INPUT -p icmp -j DROP
# Aceptamos conexiones por el puerto 80
iptables -A INPUT -p tcp --dport 80 -j ACCEPT

# Todos los paquetes que hayan llegado hasta aquí, saltarán a WAITING
iptables -A INPUT -j WAITING

### Estado KNOCK1 - En espera del primer golpe
iptables -A KNOCK1 -j LOG --log-level 4 --log-prefix "Knock 1: "
iptables -A KNOCK1 -p tcp --dport 1111 -m recent --name PASSED1 --set -j DROP
iptables -A KNOCK1 -j DROP

### Estado KNOCK2 - A la caza del segundo golpe
iptables -A KNOCK2 -m recent --name PASSED1 --remove
iptables -A KNOCK2 -j LOG --log-level 4 --log-prefix "Knock 2: "
iptables -A KNOCK2 -p tcp --dport 3333 -m recent --name PASSED2 --set -j DROP
iptables -A KNOCK2 -j KNOCK1

### Estado KNOCK3 - El último paso hacia la libertad
iptables -A KNOCK3 -m recent --name PASSED2 --remove
iptables -A KNOCK3 -j LOG --log-level 4 --log-prefix "Knock 3: "
iptables -A KNOCK3 -p tcp --dport 2222 -m recent --name PASSED3 --set -j DROP
iptables -A KNOCK3 -j KNOCK1

### Estado ACCESSGRANTED - Tenemos acceso al puerto 22
iptables -A ACCESSGRANTED -m recent --name PASSED3 --remove
iptables -A ACCESSGRANTED -j LOG --log-level 4 --log-prefix "Access Granted: "
iptables -A ACCESSGRANTED -p tcp --dport 22 -j ACCEPT
iptables -A ACCESSGRANTED -j KNOCK1

# Ahora establecemos las políticas por las que entramos en cada estado
iptables -A WAITING -m recent --rcheck --seconds 30 --name PASSED3 -j ACCESSGRANTED
iptables -A WAITING -m recent --rcheck --seconds 10 --name PASSED2 -j KNOCK3
iptables -A WAITING -m recent --rcheck --seconds 10 --name PASSED1 -j KNOCK2
iptables -A WAITING -j KNOCK1

Cómo hacer el golpeo de puertos

En el lado del cliente, en nuestro ordenador, sólo tendríamos que intentar acceder a los puertos, con el programa que queramos, incluso con un navegador web si queremos (puede ser muy lento). Eso sí, como los puertos no tienen ningún servicio y cuando intentamos establecer una conexión parece que el servidor nos ignora tendremos que tener muy en cuenta los timeouts. Es decir, tenemos que poner un timeout pequeño, por ejemplo de un segundo o menos. Eso sí, la conexión con el servidor debe ser más o menos buena para que los paquetes lleguen en un tiempo razonable. Así que podríamos intentarlo incluso con curl:

curl -m 1 http://servidor.com:1111
curl: (28) Connection timed out after 1001 milliseconds
curl -m 1 http://servidor.com:3333
curl: (28) Connection timed out after 1001 milliseconds
curl -m 1 http://servidor.com:2222
curl: (28) Connection timed out after 1001 milliseconds
ssh usuario@servidor.com
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-83-generic x86_64)

Aunque, ya puestos, intentémoslo con netcat, que será más sencillo:

nc -w 1 servidor.com 1111
nc -w 1 servidor.com 3333
nc -w 1 servidor.com 2222
ssh usuario@servidor.com
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-83-generic x86_64)

Y también con el mismo nmap, desactivando el ping al host:

nmap -Pn --host_timeout 500 --max-retries 0 -p 1111 servidor.com
nmap -Pn --host_timeout 500 --max-retries 0 -p 3333 servidor.com
nmap -Pn --host_timeout 500 --max-retries 0 -p 2222 servidor.com
ssh usuario@servidor.com
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-83-generic x86_64)

Por si fuera poco, lo podemos automatizar con un pequeño bucle en una sola línea de nuestro shell favorito:

for port in 1111 3333 2222; do nc -w 1 servidor.com $port; done
ssh usuario@servidor.com
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-83-generic x86_64)

Utilizando un demonio (knockd)

Aunque existen varios sistemas, uno de los demonios que más me ha convencido, y más utilizado para estas cosas es knockd. El problema de tener un demonio corriendo es que si el demonio se cae estamos perdidos… ¡pero perdidos de verdad! Porque no vamos a poder entrar en nuestro servidor. Hay proveedores, como por ejemplo DigitalOcean que te permiten acceder a través de web a tu servidor sin necesidad de SSH aunque en ocasiones esto debe estar prohibido por exigencia de nuestros clientes o de normativas de seguridad.

De todas formas, el hecho de utilizar un demonio muy conocido y muy antiguo para eso (la primera versión es de 2004) es que está muy probado y podemos considerarlo estable. Además está hecho en C, por lo que la huella en memoria y la CPU utilizada serán ínfimas (que esté hecho en C no tiene nada que ver, podemos hacer programas muy poco eficientes, pero programarlo en python, por ejemplo ralentizaría mucho el sistema en caso de que entren muchas conexiones a la vez); y utiliza la biblioteca pcap, que también es muy robusta y está muy probada.

Cabe comentar que el uso del demonio facilita muchísimo la tarea de implementar el port knocking, ya que es mucho más intuitivo y no hay que andar pensando en los estados de los diferentes paquetes ni preocuparse en exceso por los firewalls. Además, como el resultado final es la ejecución de un comando, es decir, el programa detecta los knocks y en consecuencia ejecuta una orden, podemos personalizar dicha orden para que sea lo más amigable posible con nuestro firewall o script controlador o si, por ejemplo, queremos o necesitamos logear este tipo de actividad, no sólo haciendo caso al propio log de knockd.

Para esta guía he utilizado una Debian, de todas formas en otra distribución no debería ser muy complicado instalarlo y configurarlo (los ficheros de configuración serán iguales).

sudo apt-get install knockd

Lo primero es editar el archivo /etc/default/knockd, en el que debemos poner a 1 START_KNOCKD y en KNOCKD_OPTS definir el interfaz de red sobre el que nos vamos a centrar. En mi caso, eth0. Por lo que el archivo quedaría así:

################################################
#
# knockd’s default file, for generic sys config
#
################################################

# control if we start knockd at init or not
# 1 = start
# anything else = don’t start
#
# PLEASE EDIT /etc/knockd.conf BEFORE ENABLING
START_KNOCKD=1

# command line options
KNOCKD_OPTS=”-i eth0″

Seguidamente, editamos /etc/knockd.conf, vamos a hacer algunos cambios en el fichero para hacer las cosas más fáciles:

[options]
UseSyslog

[openSSH]
sequence = 1111,3333,2222
seq_timeout = 5
command = /sbin/iptables -I INPUT -s %IP% -p tcp –dport 22 -j ACCEPT
tcpflags = syn

[closeSSH]
sequence = 3333,2222,1111
seq_timeout = 5
command = /sbin/iptables -D INPUT -s %IP% -p tcp –dport 22 -j ACCEPT
tcpflags = syn

Ahora, configuramos iptables, en mi caso, aunque podríamos hacerlo perfectamente desde nuestro firewall o script de firewall siempre y cuando adaptemos bien las reglas. Sería una configuración parecida a la configuración inicial anterior en la que aceptamos los paquetes de las conexiones ya establecidas, aceptamos los paquetes que vienen de localhost y lo que venga del puerto 80, si queremos. Eso sí, en lugar de enviar los paquetes enviados a WAITING, directamente los tiramos (drop).

iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j DROP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -j DROP

En este caso, knockd será el que controle si se están haciendo los knocks y la forma de entrar en el servidor será muy parecida. Eso sí, con esta configuración necesitamos hacer los knocks a los puertos 1111, 3333, 2222 para abrir el puerto 22 y luego 3333, 2222, 1111 para cerrarlo. El puerto se mantendrá abierto hasta que lo cerremos. Por un lado está bien, porque no tendremos que repetir los knocks para abrir otra sesión (por ejemplo si queremos abrir dos sesiones), pero por otro… personalmente soy muy despistado y se me puede olvidar cerrar el puerto.

Knockd por tiempo, como hacíamos con iptables

Como soy muy despistado, con knockd también podemos replicar el comportamiento anterior de iptables, es decir, el puerto se abrirá durante un tiempo y se cerrará automáticamente. Para eso, entraremos en el archivio /etc/knockd.conf y pondremos lo siguiente:

[options]
UseSyslog

[opencloseSSH]
sequence = 1111, 3333, 2222
seq_timeout = 15
tcpflags = syn
start_command = /usr/sbin/iptables -I INPUT -s %IP% -p tcp –syn –dport 22 -j ACCEPT
cmd_timeout = 10
stop_command = /usr/sbin/iptables -D INPUT -s %IP% -p tcp –syn –dport 22 -j ACCEPT

De esta manera con nuestra secuencia de puertos de antes se abrirá el puerto 22 por unos segundos y luego se cerrará pasados unos segundos. Y también tenemos un log de todo esto, tanto en el ejemplo anterior como en este podremos ver en el syslog (/var/log/syslog) lo siguiente:

Aug 3 12:43:06 ubuntu-512mb-fra1-01 knockd: xxx.xxx.xxx.xxx: openSSH: Stage 1
Jul 30 12:43:07 ubuntu-512mb-fra1-01 knockd: xxx.xxx.xxx.xxx: openSSH: Stage 2
Jul 30 12:43:08 ubuntu-512mb-fra1-01 knockd: xxx.xxx.xxx.xxx: openSSH: Stage 3
Jul 30 12:43:08 ubuntu-512mb-fra1-01 knockd: xxx.xxx.xxx.xxx: openSSH: OPEN SESAME
Jul 30 12:43:08 ubuntu-512mb-fra1-01 knockd: openSSH: running command: /sbin/iptables -I INPUT -s xxx.xxx.xxx.xxx -p tcp –dport 22 -j ACCEPT

Y listo. Ya tenemos la puerta abierta. Lo bueno de este demonio es que podemos configurar varios locks y los servicios que queramos en cada uno de ellos o incluso… enviar mensajes a los servidores utilizando los knocks, los start_command y stop_command pueden, perfectamente, ser scripts que hagan cualquier cosa y no sólo abrir o cerrar puertos.

Secuencias de knock preconfiguradas

¡El comportamiento más peliculero de knockd! Vamos, contraseñas de knock que van cambiando cada vez que se abren o se cierran las puertas. Imaginad que vamos a utilizar varias secuencias para abrir un puerto, así que vamos a crear un fichero /etc/ssh_knock_seq que contenga lo siguiente:

1111,3333,2222
1111,2222,3333,
1234,5678,9101,
1213,1415,1617

Es muy importante el espacio al principio de la línea, ya que knockd escribirá un # en el primer carácter de cada secuencia que vaya utilizando (por eficiencia, es mucho más rápido cambiar un carácter que insertarlo, ya que implica un desplazamiento de todos los contenidos), por lo que perderíamos el primer número si no ponemos un espacio.

Ahora, en /etc/knockd.conf hacemos lo siguiente:

[options]
UseSyslog

[openSSH]
one_time_sequences = /etc/ssh_knock_seq
seq_timeout = 5
command = /sbin/iptables -I INPUT -s %IP% -p tcp –dport 22 -j ACCEPT
tcpflags = syn

La primera vez que hagamos la secuencia de knocks debemos hacerla a los puertos 1111, 3333 y 2222; la segunda vez a los puertos 1111, 2222 y 3333 y así sucesivamente. Lo bueno es que las secuencias cambian, así que si alguien está sniffando el tráfico de red y vea que estás hacieno knocks a puertos e intente hacer lo mismo después, no le va a servir porque la secuencia ha cambiado. Lo malo es que si hay más de un administrador del sistema que tiene que entrar, es un poco rollo saber por qué combinación vamos.

Complicando un poco los knocks

Este cliente, al funcionar capturando el tráfico de la red, puede ver directamente los paquetes TCP que se envían, y podemos reaccionar ante diferentes flags del paquete: syn, ack, fin, rst… Para no entrar en mucha profundidad, normalmente cuando se inicia una conexión TCP. Un cliente envía SYN al servidor, y el servidor responde SYN-ACK, luego el cliente le responde ACK, cuando alguien corta la comunicación le dice al otro FIN, para reiniciar la conexión se envía RST y, bueno, hay algunos más. El caso es que normalmente cuando hacemos knocks enviamos un SYN, pero, ¿y si hacemos que el servidor requiera knocks con ACK? Es raro, más paranoico y enrevesado, además puede requerir privilegios de administración en los clientes y no sé yo si a muchos firewalls les gustará que les mandes un ACK sin conocer de nada al host con el que hables (el SYN solo es como un saludo). Pero bueno, podemos decirle a knockd en tcpflags que escuche solo a ACK o a FIN y sólo reaccionará ante esos estímulos. Está muy chulo para probar.

Para hacer los knocks, recomiendo la herramienta hping3 que podemos utilizarla así:

hping3 -c 1 -A miservidor.com -p 1111
hping3 -c 1 -A miservidor.com -p 3333
hping3 -c 1 -A miservidor.com -p 2222

Y, por supuesto, podemos utilizarla en un script como el de antes.

Envío de knocks con cliente

knockd proporciona un cliente llamado knock (sin la d) con el que podemos enviar los knocks todos seguidos y sin scripts como hacíamos antes:

knock miservidor.com 1111 3333 2222

Anexos

Quiero incluir algunas cosillas más, para tener toda la información en este post. Con algunas cosas que no tienen mucho que ver, pero ayudan a comprenderlo todo.

Ejemplo de dmesg

El kernel está todo el rato escribiendo mensajes con lo que sucede. Y el caso de iptables, ya que actúa en el kernel no es menos. Cuando desde iptables enviamos al log un mensaje podremos consultarlo haciendo:

iptables | tail

Con tail sacamos las últimas líneas del buffer de mensajes del kernel, porque suele ser muy largo y seguramente algo que sucedió hace mucho tiempo no nos interesa.
De todas formas, las fechas y horas están expresadas en el tiempo transcurrido tras el arranque de la máquina y no es un formato muy agradable porque usa números muy largos. Podemos expresarlo en formato humano con este post o con el argumento -T. Aunque a mí me gusta mucho la salida producida por:

dmesg -HTxL

Que escribe los mensajes de manera legible y nos deja hacer búsquedas (como less), el tiempo en formato humano, nos da información sobre el tipo de mensaje y encima lo muestra en colores.

Ejemplo de iptables centrado en interfaz

No voy a extenderme mucho, pero si queremos que una regla de iptables se aplique a los paquetes que viajan por una interfaz de red determinada debemos incluir “-i [interfaz]”. Al final se quedaría algo así:

iptables -A KNOCK1 -i eth0 -p tcp --dport 1111 -m recent --name PASSED1 --set -j DROP

ACCEPT, REJECT y DROP

Está claro que ACCEPT directamente acepta el paquete y si va para alguna aplicación, ésta lo recibe y lo procesa. Vamos, el uso normal. Pero cuando especificamos REJECT, estamos rechazando el paquete, y para rechazarlo tenemos que responder algo. DROP, en cambio ignora el paquete, lo tira en saco roto y que se pierda en la inmensidad de la red.
Es como cuando mantienes una conversación con alguien, si aceptas su mensaje haces algo al respecto. Te dicen “Hola” y respondes: “Buenos días, ¿cómo estás?”. Si rechazas los mensajes (reject), te dicen “Hola” y contestas: “¡No hablo contigo!” y pones carita de enfado. Eso implica un esfuerzo, intención de contestar y en general gasto de recursos. Pero si ignoras a alguien, te dirán “Hola” y no contestas, pero tú estás a lo tuyo, lo mismo has movido un poco la oreja o has girado ligeramente la cabeza cuando lo has escuchado, pero tu “amigo” ya puede esperar tu respuesta, que lo hará hasta que se cumpla su timeout.

Foto principal: Mahkeo

También podría interesarte...

Leave a Reply