Port Kn0cking

Bienvenidos amigos, en este artículo intentaré explicar que es el PortKnocking y como podemos implementarlo en nuestros sistemas. Todo se hará en un sistema Linux Gentoo con Kernel 2.6.15, aunque en principio es generalizable para el resto de Sistemas Operativos, los programas que se comentarán y los ejemplos serán para Linux :P ( Y tal vez otros sistemas).

published in #96 as Portknocking

Introducción

Empecemos haciendo un breve resumen de las medidas básicas de seguridad. Como todos sabemos para mantener nuestros sistemas seguros lo ideal y más importante es mantenerlo actualizado, además de mantener una política de contraseñas dinámica que cada X tiempo obligue a cambiar las palabras clave por otras. Pero no sólo eso, sino que además las contraseñas deberían cumplir 3 principios básicos:

  • Longitud considerable ( >8 )
  • Letras/Símbolos/Números
  • No intuitivas
Además también tiene gran importancia el procurar mantener unos permisos bien asignados para evitar usos indebidos del sistema o accesos no autorizados a ficheros supuestamente "protegidos" y "secretos".

Si habláramos de servicios locales no podemos interferir en muchos otros aspectos, por otra parte, si tenemos servicios remotos la cosa ya cambia. Podemos jugar ahora con los puertos, algunos administradores optan por cambiar el servicio de puerto para que parezca que es otro o simplemente para evitar ser detectado por un escaner. Si no tenemos el sistema actualizado, el hecho de cambiar el puerto es una tontería porque tarde o temprano se encontrará y si eso ocurre, tu seguridad se va por el retrete. Otra medida que se toma en algunas ocasiones es simplemente cambiar la presentación inicial del servicio (el "banner"), de esta manera conseguimos que al conectar salga, por ejemplo:
Connected to FTP-Indestructive V.5.9
Y si alguien intentara buscar un exploit para dicho servidor FTP no lo encontraría, simplemente porque no existe y puede ser que se trate de un servidor más común como "PROftpd", esta es otra solución poco eficiente, no es tan fácil engañar a alguien. Aún si ponemos el nombre de un servidor conocido más "seguro" que el que tenemos, es cuestión de tiempo el que sea descubierto. Más que nada porque el banner no es la única manera de identificar un servicio.

Hay otra medida de protección alternativa bastante interesante, el PortKnocking, que además nos permite inventarnos métodos personalizados al máximo, veamos un poco de que se trata.

Port Knocking

El PortKnocking consiste en prohibir el acceso a los puertos y abrirlos sólo cuando sea necesario. Para poder abrir dichos puertos habrá que "llamar a la puerta" (puerto en este caso), de ahi el nombre. Más concretamente lo que se hace es arrancar un demonio en el servidor que se encarga de analizar el tráfico entrante en algunos puertos específicos que están cerrados. Estos son analizados por dicho demonio, si están correctamente formados y han llegado en el orden correcto se nos permitirá acceder al puerto, el propio programa nos lo abrirá o dará la orden de hacerlo.

En un principio el método consistía simplemente en llamar a un seguido de puertos predefinidos en un corto periodo de tiempo y así el servidor era capaz de identificar al usuario. Pero claro, esto es como cuando nos ponemos de acuerdo en usar un ritmo al llamada a una puerta "privada", basta con que otra persona simplemente la escuche para poder reproducirla. Por eso se fueron haciendo varias modificaciones al método, algunas bastante paranoicas por cierto.

A diferencia de las otras ocultaciones, el PortKnocking mantiene los puertos cerrados hasta recibir una llave ("key"). Con lo cual, si alguien nos pasa un escaneador de puertos como por ejemplo "nmap", no verá nada abierto. Eso hace que sea mucho más efectivo, de hecho la única manera de poder entrar por la puerta es sniffar el tráfico mientras algún usuario esté accediendo. Y por supuesto tener la secuencia completa y no sólo fragmentos.

LISTADO 1

CARACTERÍSTICAS

Port Knocking se puede resumir en las siguientes carácteristicas, que pueden ser vistas como ventajas:

- Autenticación del usuario basado en métodos de Firewall.
- Establecer conexiones con un host sin que haya puertos abiertos con un uso innovador de los puertos cerrados.
- Cierra tu red previniéndola de los perfiles remotos ( hacer un dibujo de la red sin tener acceso físico ).
- No se puede saber los puertos que afectan al PortKnocking ni como éste ha sido implementado.
- Complican la detección de sniffing añadiendo ruido.
- Secuencias cifradas para mejorar la seguridad.
- Beneficios del control de acceso proporcionadas por sistemas de Firewall e IDS.
Este escenario se dará muchas menos veces que un barrido de puertos y si los usuarios son cuidadosos esto no pasará prácticamente nunca. En el listado1 vemos una traducción de las características, se pueden encontrar en la web "oficial":
http://www.portknocking.org
En el apartado "features" vemos la lista de características que hacen del método, un método bastante seguro y según la web podría suponer una barrera completa, ya que el método más seguro es cerrar todos los puertos, y por defecto esto es lo que nos hace este sistema, sólo los abre cuando toca, aunque en mi opinión siempre es mejor complementar barreras que no destinar la suerte a sólo una que parezca mágica.

En la imagen1 podemos ver el funcionamiento esquematizado del sistema PortKnocking. Fijaros en el detalle, después de la secuencia de puertos, solo se permite el paso a la IP solicitante, por lo que si otra IP intentara conectar a dicho puerto sería rechazada.

IMAGEN 1


Vamos a ver como podemos ofrecer una funcionalidad similar conociendo sólo lo explicado hasta ahora simplemente con las herramientas estandards de Linux.

Implementación Propia

Veamos como podemos implementar dicho método, no hay espacio suficiente para hacer una explicación detallada del proceso de programación, así que serán indicaciones e ideas conceptuales.

Primero debemos elegir un lenguaje que nos sea cómodo, teniendo siempre en cuenta el ámbito que tendrá el programa, es decir, es tontería utilizar un lenguaje como C para implementar un servidor que sólo vamos a usar nosotros en nuestra casa existiendo Perl o Python (por ejemplo) que nos facilitarían muchísimo las cosas. Si usamos C tendremos un servidor más robusto y fuerte, además será capaz de funcionar con una cantidad mayor de conexiones simultaneas y con una mayor rapidez, eso puede ser útil para una empresa de muchos usuarios, pero no para algo más humilde o una empresa pequeña.

Segundo debemos elegir qué tipo de identificación queremos, si queremos añadirle cifrado, si queremos una secuencia larga, corta, puertos siempre fijos, que vayan variando.... Aquí es donde entra en juego la creatividad de cada uno, de hecho este aspecto es el que más interesante me resulta del PortKnocking, el poder diseñar el método de identificación como queramos.

Hasta aquí todo muy bien, tenemos un lenguaje y sabemos como queremos identificarnos, pero ahora tenemos el problema de leer los paquetes entrantes, como estamos suponiendo que usamos Perl o Python, lo más rápido es usar los logs de un "sniffer" que esté escuchando en los puertos que nos interesan. Otra posible solución sería usar librerías que nos sirvan para el mismo propósito ( por ejemplo: lib pcap ).

Analicemos un momento lo que tenemos ahora. Un simple programa que detecta si le llegan o no unos ciertos paquetes... Esto no nos sirve, lo más importante de todo esto és la función firewall, ya que esta es la que realmente nos proporciona una sensación de seguridad. Lo interesante del método es que el puerto se abre exclusivamente para la IP solicitante y durante un corto periodo de tiempo, cómo podemos conseguir esto? Pues añadiendo reglas a IPtables. En un principio todas las conexiones son rechazadas con lo que en la tabla e IPTables habrá algo parecido a:
LynaXa ~ # iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  anywhere             anywhere            state NEW

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
LynaXa ~ #
Así que, cada vez que tengamos la "key" o secuencia, sólo tendremos que añadir una regla que permita el paso de la IP especificada al puerto que queramos. Finalmente al cabo de X segundos se quita esta regla, con lo que volvemos a tener un "DROP" general. Para añadir una IP origen ("src") a IPtables, basta con la siguiente sentencia:
LynaXa ~ # iptables -I INPUT -s 127.0.0.1 -m state --state NEW -j ACCEPT
LynaXa ~ # iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     all  --  localhost            anywhere            state NEW
DROP       all  --  anywhere             anywhere            state NEW
Como vemos ha sido agregada justo antes, con lo que si una conexión cumple con la primera regla, ya pasará, el orden es importante. Después hay que eliminar dicha regla al cabo de X segundos:
LynaXa ~ # iptables -D INPUT -s 127.0.0.1 -m state --state NEW -j ACCEPT
Ahora ya tendremos de nuevo la tabla como al principio. Además nuestra implementación, tal y como dice en la web, para que sea correcta y aplicable a sistemas con un número de conexiones más o menos grande debe cumplir los tres pre-requisitos siguientes:

  • Orden de llegada no correspondido, pero hay que tenerlo en cuenta, ya que la red puede hacer "tonterías", y algunos paquetes pueden retrasarse
  • Hay que poder gestionar varias secuencias simultáneas de distintos hosts
  • Hay que tener en cuenta el impacto en el servidor y de los paquetes entrantes tanto en el sistema como en la red. De lo contrario podría saturarse.
Si hacemos caso de estas tres características nuestra implementación ya será completamente funcional :). Claro que también se nos ofrecen multiples implementaciones, en la propia web tenemos una gran lista en el siguiente enlace:
http://www.portknocking.org/view/implementations
A continuación vamos a echarle un ojo al PoC (Proof Of Concept) que nos ofrece la web. Este programa viene con un servidor y un cliente y viene a ser un prototipo del sistema, con lo que es áltamente configurable.

PoC de PortKnocking ( Prototipo en Perl )

Para poder disponer de dicho programa, lo primero que debemos hacer es bajárnoslo de la siguiente URL:
http://www.portknocking.org/distribution/portknocking-0.30.tgz
Sería bueno bajarse también el "md5" por si acaso:
http://www.portknocking.org/distribution/portknocking-0.30.md5
Una vez tengamos los dos ficheros habría que mirar si realmente nos hemos bajado lo que esperábamos, ya que si no es así, como el servidor se ejecuta como "root" podríamos sufrir consecuencias inesperadas.

Para comprobar el md5 ejecutamos:
$ md5sum -c portknocking-0.30.md5
portknocking-0.30.tgz: OK
$
Como vemos todo correcto :). Ahora descomprimos el tgz ("tar -xzvf file"). Vamos al directorio que se nos crea y nos miramos el README. Allí nos dice que debemos retocar los ficheros "knockdaemon.conf" y "knockclient.conf" para personalizar nuestro servidor. Teniendo siempre en cuenta que debemos poner el mismo sistema de Cifrado y la misma contraseña en ambos (obviamente), análogamente hay que poner acorde la secuencia de entrada. Vamos a configurar primero el cliente y luego el servidor, fijémonos que todo se basa en asignar valores a variables y añadir datos a la estructura "XML" o bien editarlos.

Configurando el Cliente

Para modificar la configuración del cliente vamos a modificar el fichero "knockclient.conf", lo abrimos y lo primero que vemos que nos puede interesar modificar es lo siguiente:
remote  = 127.0.0.1
Esta línea corresponde a la IP del servidor al que queremos conectar, como en este caso será una prueba local ya está bien el que nos viene por defecto. Seguimos mirando y vemos lo siguiente:
client  = 10.1.1.1
port    = 22
flag0   = 5
flag1   = 15
flag2   = 20
Que son: la IP a la que se desea dejar pasar (la gracia es que no es necesario que sea la misma que hace la petición), el puerto a abrir y los flags que son enviados en las peticiones, luego el servidor dependiendo de éstos hará unas acciones u otras. Vamos a cambiar la IP a "127.0.0.1".

Más adelante encontramos las opciones de cifrado:
encrypt = yes
key     = password
cipher  = Blowfish
Aquí vemos que podemos elegir si queremos cifrar o no, además podemos personalizar tanto la contraseña que queremos usar como el algoritmo de cifrado. Cambiaremos la key a "arroba" en vez de "password" que es muy triste jeje. En cambio dejaremos el tipo de cifrado tal y como está, recordad que si éste se cambia deberemos instalar los correspondientes módulos de Perl (lo pone en el README). Lo demás lo dejaremos tal y como está. Además si usamos el comando "./knockclient --man" tendremos toda la ayuda que necesitemos. Bueno, ahora vamos a por la configuración del servidor.

Configurando el Servidor

Primero abrimos el correspondiente fichero de configuración "knockdaemon.conf" y veamos que nos aparece. Como podemos ver hay infinidad de opciones, así que trataremos sólo las más básicas, para saber modificar el resto mirad la web o usad el comando "knockdaemon --man". Si avanzamos un poco en el fichero veremos la siguiente entrada:
interface      = lo
La cual indica la interfaz de red, como en este caso se trata de una prueba local, ya está bien, si no fuera así deberíamos usar otras como "eth0, eth1,eth2...". Después de esto tenemos varias opciones, como por ejemplo las acciones a tomar en cada secuencia correcta y otras muchas cosas más.

Finalmente encontramos otra vez las opciones de cifrado (igual que en el caso del cliente).
decrypt = yes
key     = password
cipher  = Blowfish
Análogamente al cliente, en este caso también vamos a cambiar la contraseña a "arroba" para poder descifrar todo lo que nos llegue. El resto de opciones ya nos sirven tal y como se encuentran por defecto. Vamos pues a probarlo :)

Probando la aplicación

Primero vamos a arrancar el servidor ( como root ) y luego intentaremos hacer una conexión con el cliente. Claro que antes que nada debemos añadir las reglas necesarias a IPtables para denegar toda conexión entrante, para ello hacemos:
# iptables -I INPUT -m state --state NEW -j DROP
Y ya está, ya tenemos todos los puertos perfectamente asegurados. Ahora arrancamos el servidor y veremos como nos da muchísima información de debug (ver listado2).

LISTADO 2

LynaXa pknock_prototype # ./knockdaemon
debug port span ports 16384 log2 14
debug number of fields IP 4
debug number of fields PORT 6
debug number of fields FLAG0 7
debug number of fields CHECKSUM 8
debug expected queue length 16
debug expected modified queue length 10
debug validated knock length 10
debug runmode is network
debug applying packet filter tcplo
debug linklayer: ethernet interface detected
debug loaded state cache from /tmp/portknock.cache
debug starting to listen on lo
Si nos aparece el siguiente mensaje:
debug starting to listen on lo
Significa que todo ha salido correctamente y que por lo tanto ya podemos arrancar el cliente. Al hacerlo vemos como el servidor también reacciona y va controlando todos los pkts que le llegan, en el listado 3 vemos un trozo de la salida del cliente y la reacción del demonio. Ahí se observa como después de hacer algunas comparaciones finalmente se realiza la acción que queríamos. Las acciones se definen usando XML, en el listado4 vemos un esquema básico de como funciona.

LISTADO 3

sws@LynaXa ~/pknock_prototype $ ./knockclient
debug raw knock data n=8 127 0 0 1 0 22 5 155
debug encrypting data with Blowfish
debug cipher binary encoded n=10 321 8798 10311 7319 2521 15352 8016 8919 9275 0
debug knock sequence n=10 8321 21796 23309 18318 13520 28350 21014 21917 22273 8000
knocked on 127.0.0.1:8321
knocked on 127.0.0.1:21796
knocked on 127.0.0.1:23309
knocked on 127.0.0.1:18318
knocked on 127.0.0.1:13520
knocked on 127.0.0.1:28350
knocked on 127.0.0.1:21014
knocked on 127.0.0.1:21917
knocked on 127.0.0.1:22273
knocked on 127.0.0.1:8000

Y en el servidor al mismo tiempo vemos:
debug parsed yes client IP [127.0.0.1] and knock port [8321]
debug portspan inside knock port 8321
debug Thu Jun  8 03:55:13 2006 knock from [127.0.0.1] on [8321]
debug queue [127.0.0.1] n=1 8321
debug parsed yes client IP [127.0.0.1] and knock port [21796]
debug portspan inside knock port 21796
debug Thu Jun  8 03:55:14 2006 knock from [127.0.0.1] on [21796]
debug queue [127.0.0.1] n=2 8321 21796
debug parsed yes client IP [127.0.0.1] and knock port [23309]
debug portspan inside knock port 23309
[...]
debug condition 22 == 22 && 5 > 0 && 5 < 255
debug condition satisfied - looking for templates iptables_close
debug template iptables_close
debug command /sbin/iptables -D INPUT -p tcp -s 127.0.0.1/32 -d 0/0 --dport 22
--syn -j ACCEPT
debug actiontest delayed 50 time 20060608040411 /sbin/iptables -D INPUT -p tcp
-s 127.0.0.1/32 -d 0/0 --dport 22 --syn -j ACCEPT
[..]
debug current queue

LISTADO 4

<actions>
   #Acción que se realiza al detectar la conexión
   <action>

      #Si se cumple dicha condición
      condition = PORT == 22 && FLAG0 < 255

      #Templates definidos más abajo
      template  = iptables_open
      template  = incr_num_visits
      template  = prev_visit
      use       = yes
   </action>
   <action>

      #Si se cumple dicha condición
      condition = PORT == 22 && FLAG0 > 0 && FLAG0 < 255

      #Ejecutamos la acción definida en este template con un retraso (Delay)
      #de “FLAG0*60” segundos
      template  = iptables_close
      delay     = FLAG0*60
      use       = yes
   </action>
</action>
[..]
#Definición de templates con los comandos que hay que ejecutar.
<template iptables_open>
system = "/sbin/iptables -I INPUT -p tcp -s IP/32 -d 0/0 --dport PORT --syn -
j ACCEPT"
</template>
[..]

Conclusiones

Hasta aquí el artículo de Introducción al Port Knocking, os recomiendo que leáis sobre este método, es realmente interesante todo lo que puede llegarse a derivar de la comunicación con puertos cerrados, además el propio servidor que se ha usado es infinitamente configurable, podemos hacer cualquier acción inmediata y otra retrasada X tiempo a continuación, no tiene porque ser simplemente abrir y cerrar puertos, de hecho podríamos controlar una máquina entera sólo con un servidor de este estilo. Enviando paquetes a puertos y que éste reaccionara.

Además la configuración usando "XML" no es tan complicada como puede parecer en un principio, de hecho con un par de ojeadas acostumbra a haber suficiente. Aunque en este caso no hemos tocado la secuencia de llamada, también se puede cambiar, por supuesto.

Pues eso es todo amigos, ah sí! No se si alguno me ha escrito alguna duda o algo (sinceramente lo dudo) pero por si acaso, que todos sepan que el servidor de "www.disidents.com" ha tenido varios problemas, de hecho aun los tiene, así que por el momento, mandadme los mails a la dirección de este artículo. Si hay algun cambio ya lo notificaré. Leed y aprended!

EOF