Información blog

Linux, tutoriales, noticias, sistemas, redes y seguridad informática, entre otras cosas.

martes, 21 de marzo de 2017

Cómo crear paquetes de red desde cero en Linux

Cuando estamos realizando alguna prueba de red; ya sea para testear un cortafuegos o para hacer alguna tarea de debugeo, a veces tenemos la necesidad de tener que realizar tareas muy concretas que una aplicación "convencional" no puede ofrecernos; pues podemos tener que necesitar usar un protocolo IP o puerto concreto que no es fácil de emular de forma convencional. Es por ello que a veces necesitamos tener que hilar muy fino y recurrir a aplicaciones que nos puedan permitir crear paquetes de red de forma precisa y personalizada. Aquí es donde entra en juego una aplicación que, si bien he de admitir que inicialmente no es muy intuitiva, nos ofrece un enorme control sobre los paquetes de red; ya sea para crearlos desde cero, como para editar unos existentes: Dicha herramienta, desarrollada en Python, se denomina Skapy y hoy quiero enseñaros como desenvolvernos con ella para, por lo menos, saber defendernos a la hora de tener que usarla.

Portada_crear_paquete_red

Scapy es una herramienta usada puramente en la consola pero a la que no hay que tenerle miedo, ya que si bien inicialmente tiene una cierta curva de aprendizaje, tras un par de pequeñas pruebas comprobareis que podréis realizar, al menos, las pruebas y operaciones más básicas. Se trata de una herramienta que no está instalada por defecto en ningún sistema Linux ni tampoco en en ningún repositorio oficial, con lo que tiene que ser bajada desde la página oficial de ésta. La forma más ágil de hacerlo, en mi opinión, es desde la consola, ya que es tan sencillo como escribir:

wget https://github.com/secdev/scapy/archive/v2.3.2.zip

Al ser una utilidad comprimida en formato, zip; sería necesario tener ésta utilidad instalada en el sistema, cosa muy sencilla ya que está incluida en los repositorios oficiales. Después, simplemente habría que descomprimir el archivo descargado mediante el comando unzip. Todo esto se traduce en dos simples acciones:

  1. apt-get install zip
  2. unzip v2.3.2.zip

Del fichero descomprimido obtendremos una carpeta llamada scapy-2.3.2 en la que tendríamos que entrar para poder ejecutar su binario, llamado run_scapy.

Ahora bien; antes de continuar hay que tener claro qué es lo que necesitamos para crear nuestro paquete. Todo paquete de red requiere (explicado de forma muy resumida) de dos elementos:

  • Una cabecera IP: La cabecera IP es un conjunto de reglas o campos que se usan para transmitir el paquete de un lugar a otro. En concreto son 13 campos: La versión IP, longitud del paquete, tipo de servicio, longitud total, identificador, flags (etiquetas), Offset, tiempo de vida, protocolo, checksum, IP origen, IP destino y opciones. A veces lleva también un campo extra usado para "rellenar" el paquete para que así tenga un tamaño que sea múltiplo de 32 bits. Si bien todos los parámetros son importantes, los que más nos interesan al usar Scapy son: La versión IP (IPv4 o IPv6), el protocolo, la IP de origen y la IP de destino. 

Composicion_cabecera_IP
Composición cabecera IP

  • Payload: El payload serían los datos que se quieren incluir dentro del paquete; datos que pueden ser de cualquier tipo; desde un texto plano a una imagen. Dicho mensaje no es siempre necesario, pues dependiendo de la prueba que queramos hacer tal vez no nos interese en absoluto el contenido del paquete, sino que simplemente éste llegue.

Teniendo esto claro, vamos a crear nuestro propio paquete. Con Scapy en ejecución, lo primero que haremos será crear el elemento más importante: la cabecera IP. Dicha creación puede parecer compleja, pero no es así: Una cabecera vacía (a modo de test) sería tal que así:

Nombre_variable=IP()

Por ejemplo:

CABECERA=IP()

Con esto Scapy ya habría creado la cabecera, cabecera que tendría una serie de parámetros asignados por defecto; muchos de los cuales nos sirven tal y como están. Para ver el contenido de nuestra cabecera, escribiremos:

CABECERA.show()

Cabecera_IP_vacia
Cabecera IP vacía

Si os fijáis bien, la mayoría de los campos están rellenados, y la mayoría, con los datos que se han introducido por defecto, serían datos válidos para una cabecera IP; a excepción de 3: proto, src y dst. Los cuales serían, protocolo, IP origen (src, o IP source) e IP de destino (dst, o IP destiny). Al protocolo le daremos un trato personalizado, tal y como veréis más adelante, pero los otros dos parámetros deben de ser introducidos. Para ello, haremos que la variable CABECERA, tenga dichos nuevos valores, tal que así:

CABECERA=IP(src='192.168.1.2',dst='192.168.1.3')

Es importante tener en cuenta que, si queremos editar en el futuro otros valores, tendremos que seguir manteniendo estos valores. Por ejemplo si deseásemos ahora que el campo flags, tuviese el valor 2; si hiciésemos esto:

CABECERA=IP(flags=2)

Sobrescribiríamos lo introducido anteriormente, ya que lo que estamos haciendo es asignar un valor a la variable CABECERA, con lo que para añadir dicho valor tendríamos que hacer:

CABECERA=IP(src='192.168.1.2',dst='192.168.1.3',flags=2)

Tenemos la cabecera casi formada, pero tal y como he comentado antes, el protocolo que ha asignado Scapy por defecto no es el correcto. Pero no valdría que le asignásemos un valor a dicho campo, ya que el protocolo es un campo que debe de ser tratado por separado debido a su relevancia. Podemos optar por uno de estos tres protocolos: ICMP, TCP y UDP. En mi caso optaré por UDP, pero se puede escoger cualquiera de ellos. Para crear un campo UDP, haríamos:

Nombre_variable=UDP()

En mi caso:

PROTOCOLO=UDP()

Ahora veremos el interior de dicho campo del mismo modo que hemos hecho con nuestra cabecera; es decir:

PROTOCOLO.show()

Protocolo_sin_datos
Protocolo_sin_datos

Tenemos el protocolo UDP "creado", pero el problema está en que no hemos dicho desde qué puerto vamos a enviar el paquete y a qué puerto queremos enviarlo. Para ello tendremos que seguir el procedimiento, de forma parecida a la anterior, y editar la variable PROTOCOLO para que sus valores sport (puerto origen) y dport (puerto destino) tengan unos valores determinados; lo cual haríamos de esta forma:

PROTOCOLO=UDP(sport=1024,dport=80)

Por último, vamos a crear un payload con algo de contenido a modo de prueba. La creación de payloads en sí no es sencillo, pero en este caso, al querer enviar un payload con un texto plano a modo de prueba, su complicación no es elevada. Para crear el payload tendremos que usar el mismo procedimiento que el que hemos hecho antes para el resto pero llamando a la "función" Raw. Función que solo tiene un campo llamado load, con lo que a diferencia de con los procesos anteriores en los que primero hemos creado la variable, luego la hemos consultado y por último hemos rellenado sus campos. Aquí directamente la crearemos con su campo rellenado:

PAYLOAD=Raw(load='Esto es una prueba ->UDP\n')

Ya tenemos todos los elementos creados, solamente habría que unificarlos para crear el paquete; para lo cual recurriremos a las variables que hemos creado. El paquete tendrá la estructura:

nombre_variable=Cabecera_IP/Protocolo/Payload

Para nuestro caso particular sería:

PAQUETE=CABECERA/PROTOCOLO/PAYLOAD

Para ver cómo ha quedado nuestro paquete escribiremos PAQUETE.show(), que mostrará la unión de todo lo que hemos creado hasta ahora:

Paquete_scapy
Paquete Scapy

Ya tenemos todo listo; ahora solamente tendremos que enviarlo mediante el comando send:

send(PAQUETE)

Lo ideal sería tener algo que nos permita detectar en tiempo real si el paquete ha sido enviado con éxito. Esto se puede lograr de dos formas. La primera es usando un analizador de red como tcpdump o Wireshark, ya sea en este equipo o en el receptor del paquete. La segunda sería usando netcat en el receptor para "escuchar" en el puerto 80 y ver qué es lo que nos llega. Lo más normal sería optar por la primera opción; con lo que si hiciésemos una captura y la abriésemos, veríamos que efectivamente nuestro paquete ha sido enviado con éxito.

Captura_Wireshark
Captura de red leída con Wireshark

Como podéis ver la creación de un paquete de red con Scapy, no es tan complicado como parece; solamente requiere tener cierto cuidado durante su "elaboración", pero gracias a la flexibilidad otorgada por esta utilidad podremos enviar paquetes de red creados de forma completamente "artesanal" cuya creación hemos controlado de principio a fin.

Espero que os haya resultado útil.

Saludos.

miércoles, 15 de marzo de 2017

Creación de túneles en Linux

La comunicación entre diferentes equipos para, ya sea mandar un mensaje, o acceder a una página web, se ha vuelto indispensable hoy en día, hasta el punto de que nadie puede conferir un ordenador sin acceso al resto de equipos de la red o a Internet. La cuestión está en que el mundo de las redes, al igual que todo en esta vida, ha ido evolucionando y adaptándose a las necesidades de hoy en día... El cambio más significativo que se ha realizado en este área, es sin lugar a dudas la implantación de IPv6, protocolo que ha ido ganando peso con el paso de los años debido a la necesidad de usarla... Aún así, el formato de IP más usado en la actualidad sigue siendo IPv4; tanto por su facilidad a nivel de gestión como por que a nivel tecnológico hay muchísimos equipos que no soportan IPv6. Esta imposibilidad de adoptar este nuevo protocolo hace que a veces sea imposible establecer una comunicación entre dos equipos, pues una IPv4 no puede comunicarse con una IPv6 ni viceversa, y debido a ello hay que encontrar soluciones que puedan lograr dicha comunicación; y es ahí donde entra en escena el concepto sobre el que os quiero hablar hoy: Los túneles.

Tunel_linux

Un túnel es una técnica que encapsula un protocolo dentro de otro, haciendo que una comunicación, que de por sí no es posible, se haga realidad. Por ejemplo podemos hacer que dos equipos que funcionan bajo IPv4 e IPv6, puedan comunicarse entre ellos con ambos protocolos, aún cuando están separados por un router que es capaz de enrutar únicamente IPv4. Existen tres tipos de túneles:

  • IPIP: Únicamente puede encapsular tráfico unicast, y además solamente permite "tunelizar" IPv4 sobre IPv4. 
  • SIT: Este tipo de túnel encapsula tráfico IPv6 sobre IPv4 además permite transmitir tanto en unicast como multicast.
  • GRE: Este es el tipo de túnel más "dinámico" y más completo, ya que permite encapsular tanto IPv4 como IPv6 sobre IPv4: permitiendo además también transmitirlo tanto en unicast como en multicast.

Teniendo en cuenta que el túnel GRE es el más completo, nos centraremos sobre éste, si bien hay que tener en cuenta que la creación de los otros dos túneles sería muy similar a la de uno del tipo GRE, con la diferencia de que en estos dos casos habría que tener en cuenta las limitaciones de dichos túneles.

Antes de crear el túnel en cuestión, veamos el siguiente diagrama con el fin de ponernos en situación;

Diagrama_tunel

Lo que tendríamos serían dos equipos, comunicados entre sí por un router que únicamente enruta IPv4, con lo que ambos equipos se comunican vía IPv4 pero no vía IPv6. Con el túnel lo que haríamos sería enviar el paquete con una cabecera IPv4 que sí que sería reconocida por el router haciendo que la comunicación IPv6 sea válida.

Para crear un túnel, sea del tipo que sea, tendremos que recurrir a la utilidad ip; utilidad estándar que está instalada por defecto en el sistema. En el equipo A escribiríamos lo siguiente:

  1. ip tunnel add tunel1 mode gre remote 192.168.2.10 local 192.168.1.10
  2. ip link set tunel1 up
  3. ip addr add fe80::d0:ffff:351e:1/64 dev tunel1
  4. ip route add ::/0 dev tunel1

Los comandos introducidos harían lo siguiente:

  • Con el primer comando estaríamos creando un túnel de tipo GRE entre nuestra IP local 192.168.1.10 y la IP remota 192.168.2.10.
  • Con el segundo comando, activamos dicho túnel, ya que por defecto estaría desactivado. Tras levantar dicho túnel, podríamos verlo como si fuese una interfaz de red más con el comando ifconfig.
  • El tercer comando asigna una IP al túnel en cuestión; IP que sería nuestra IPv6.
  • Por último, le pondríamos un gateway al túnel en cuestión.

En el equipo B, habría que hacer lo mismo, pero realizando el túnel a la inversa y asignando la IP correspondiente a la interfaz del túnel en cuestión.

  1. ip tunnel add tunel1 mode gre remote 192.168.1.10 local 192.168.2.10
  2. ip link set tunel1 up
  3. ip addr add fe80::d0:ffff:351e:2/64 dev tunel1
  4. ip route add ::/0 dev tunel1


Gracias a esta configuración, tendríamos un túnel GRE completamente operativo, pudiendo hacer ping (o cualquier otra acción) entre ambas IPv6 sin impedimento alguno, cosa que sin dicho túnel no habría sido posible.

En caso de querer eliminar nuestro túnel tendríamos que primero deshabilitar dicho túnel, para después borrarlo, lo cual se realizaría tal que así:

  1. ip route del ::/0 dev tunel1
  2. ip addr del fe80::d0:ffff:351e:2/64 dev tunel1
  3. ip link set tunel1 down
  4. ip tunnel del tunel1

Como podéis ver, los túneles son un recurso que pueden resultarnos de gran utilidad, ya que pueden hacer que conexiones "imposibles" puedan hacerse realidad; en caso de querer tener un túnel permanente, simplemente habría que realizar un script de arranque con las ordenes atrás mostradas; algo tan sencillo como (por ejemplo) crear el script tunelling.sh en /etc/init.d,/ con el siguiente contenido:

  1. #!/bin/bash
  2. # Inicia o para el tunel
  3. ### BEGIN INIT INFO
  4. # Provides:          tunel
  5. # Required-Start:    $network $remote_fs $syslog $time
  6. # Required-Stop:     $network $remote_fs $syslog $time
  7. # Default-Start:     2 3 4 5
  8. # Default-Stop:      0 1 6
  9. # Short-Description: Iniciador/parador tuneles
  10. # Description:       Script para arrancar o parar un tunel que se incluye en el arranque
  11. ### END INIT INFO
  12. case "$1" in
  13. start)  echo "Iniciando tunel"
  14.         ip tunnel add tunel1 mode gre remote 192.168.1.10 local 192.168.2.10
  15.         ip link set tunel1 up
  16.         ip addr add fe80::d0:ffff:351e:2/64 dev tunel1
  17.         ip route add ::/0 dev tunel1
  18.         ;;
  19. stop)   echo "Eliminando tunel"
  20.         ip route del ::/0 dev tunel1
  21.         ip addr del fe80::d0:ffff:351e:2/64 dev tunel1
  22.         ip link set tunel1 down
  23.         ip tunnel del tunel1
  24.         ;;
  25. *)      echo "Uso: /etc/init.d/tunelling.sh {start|stop|}"
  26.         exit 2
  27.         ;;
  28. esac
  29. exit 0

Dicho script tendría que tener permisos de ejecución y estar incluido en el arranque, cosa que haríamos mediante el uso del comando chmod (para darle dichos permisos) e insserv (para el arranque):

  1. chmod +x /etc/init.d/tunelling.sh
  2. innserv tunelling.sh

Gracias a esto no tendríamos un script que automatizaría el hecho de crear o destruir el túnel, sino que haríamos que dicho script se arrancase por defecto en el arranque, ahorrándonos la tediosa tarea de tener que "levantar" dicho túnel en cada arranque. Obviamente, el otro extremo del túnel tendría que tomar una medida similar con el fin de que el túnel se realice con éxito.

Espero que os haya resultado útil.

Saludos.

viernes, 10 de marzo de 2017

Cómo crear un Captcha para conexiones SSH

Ya ha pasado cierto tiempo desde mi último artículo, lo cierto es que me habría gustado poder haber escrito aquí mucho antes, pero debido a algunas circunstancias he estado lejos de un ordenador durante prácticamente dos semanas, habiéndome sido imposible siquiera estar informado del estado del mundo. Ahora que ya he vuelto a un periodo de relativa normalidad, espero poder retomar el ritmo poco a poco y volver publicar artículos con periodicidad, tal y como he intentado hacer hasta ahora. Dicho esto, hoy quiero traer algo que en mi opinión puede resultar interesante a más de uno; como a menudo he dicho en este blog, el servicio SSH me parece una utilidad fantástica de un valor incalculable; ya que permite conectarnos de forma segura a un equipo desde el otro lado del mundo y si uno sabe manejarse en una shell, puede hacer prácticamente de todo desde ésta. Obviamente, dicho servicio es muy atractivo, y es uno de los principales servicios atacados en los servidores Linux, con lo que debemos tomar bastantes medidas de seguridad para evitar que entren personas no gratas; medidas de seguridad que pasan desde las estándar, tales como la imposibilidad de acceder remotamente directamente como root, hasta medidas de seguridad "opcionales" o "curiosas" tales como el port knocking o SPA. Hoy os quiero hablar sobre una medida de seguridad "opcional" que guarda un pequeño parecido con el segundo factor de autenticación: Se trata del uso de Captchas durante la autenticación de un usuario vía SSH.

portada_Captcha

Un Captcha (Completely Automated Public Test to tell Computers and Human Apart) no es, ni más ni menos, que una comprobación para determinar que aquel que está intentando conectarse al equipo es un humano y no una máquina (como por ejemplo un bot que ha logrado loguearse al equipo). Esta medida es especialmente útil cuando han logrado acceder al equipo mediante herramientas automatizadas, ya que dicho Captcha requiere la intervención de un humano; intervención que generalmente es muy sencilla ya que generalmente se trata de una imagen que muestra una consecución de letras y/o números en la pantalla; consecución que uno debería introducir a mano y que en caso de ser igual que la mostrada en la pantalla, nos permitiría continuar. Esta medida de seguridad es muy popular en muchas páginas web; especialmente en formularios de registro o similares que pueden ser susceptibles a ser afectadas por bots. Un Captcha en Internet tendría un aspecto similar al siguiente:

Captcha_ejemplo
Imagen tomada de Wikipedia

Obviamente, en una shell dicha secuencia no estaría en una imagen de tal diseño, sino que sería un ASCII art que emularía una imagen, pero que en sí sería código ASCII; algo como lo siguiente:

Captcha_ASCII

Ahora bien, está función no está implementada por defecto, sino que es una funcionalidad que proviene de un módulo PAM especial; módulo que no está añadido por defecto en el sistema. Aún así, afortunadamente su implantación es increíblemente sencilla. Para ello, lo primero que haremos será instalar las dependencias necesarias para que el módulo PAM pueda ser instalado; para lo cual haríamos:

apt-get install build-essential figlet libpam0g-dev git

La primera dependencia, build-esential contendría todos los paquetes necesarios para la compilación de un archivo (gcc, make, g++...); figlet en cambio es una aplicación que permite dibujar letras en formato ASCII. Libpam0g-dev sería la dependencia de desarrollo de PAM, gracias a la cual podremos instalar nuevos módulos en éste. Por último estaría git, herramienta con la que nos descargaremos el módulo PAM.

Con las dependencias cumplidas, tocaría bajarse el módulo PAM que hará posible este Captcha: pam_captcha. Para ello, nos descargaremos el paquete desde GitHub mediante el comando:

git clone https://github.com/jordansissel/pam_captcha.git

Esto nos crearía una carpeta llamada pam_captcha en cuyo interior estaría todo lo necesario para añadir el módulo PAM a nuestro sistema. Ahora simplemente tendríamos que entrar en dicha carpeta y escribir el comando make para compilar paquete y convertirlo en un módulo usable. Dicho módulo , ya compilado, se tendría que llamar pam_captcha.so.

La ubicación a la que tendríamos que mover dicho módulo dependería del sistema operativo, pero lo que todos los sistemas tienen en común es que la carpeta que contiene dichos módulos debe de llamarse security; carpeta en cuyo interior tendrían que haber únicamente módulos PAM, con lo que el mejor comando para encontrar dicha ubicación sería:

find / -name "pam_*.so" |grep security

A sabiendas de dicha ubicación, moveríamos el módulo pam_captcha.so a ésta.

Con todos los preparativos realizados, pasaríamos a configurar el fichero de configuración de SSH con PAM; configuración que realizaría en el fichero: /etc/pam.d/sshd. En el susodicho añadiríamos esta línea al principio del todo:

auth required pam_captcha.so math randomstring

Pero con ésto solo no bastaría, ya que tendremos que cerciorarnos de que el servicio SSH en sí está preparado para trabajar con PAM. Es por eso que tendríamos que asegurarnos que el fichero /etc/ssh/sshd_config posee el parametro ChallengeResponseAuthentication con el siguiente valor:

ChallengeResponseAuthentication yes

En caso afirmativo, no habría que hacer nada mientras que si en cambio estuviese con valor no, habría que cambiarlo a "yes" y reiniciar el servicio SSH.

Con esto, cualquier usuario que se loguee remotamente vía SSH tendrá que, no solo introducir el usuario y la contraseña, sino que también tendrá que introducir el captcha, haciendo que nos cercioremos de que la persona que se está conectando es humana.

Espero que os haya resultado útil.

Saludos.