El fuzzing de redes es una técnica automática o semi-automática para cazar errores de software. La idea es sencilla: le metes al programa entradas correctas e incorrectas al azar, a saco, y ves por dónde se rompe.

network fuzzing

Como siempre queda bien decir cifras y meter miedo, yo no voy a ser menos. Así que ahí va:

Según Forbes Advisor, hubo 2,365 ciberataques en 2023 con 343,338,964 víctimas.

Ahora ya puedes decir que has leído un post de ciberseguridad serio.

Bueno. Aquí es donde el fuzzing nos echa un cable para intentar no acabar dentro de esa estadística.

La gracia está en encontrar tú los agujeros antes de que los encuentre alguien que quiera usarlos. Te da margen para arreglarlos mientras todavía son un bug y no un incidente.

Por qué es importante el fuzzing de redes en la seguridad de redes

why fuzzing

El fuzzing viene a ser un simulacro de incendio. Buscas los agujeros tú antes de que los encuentre otro y con peores intenciones, y los tapas mientras todavía puedes hacerlo con calma. No sustituye a nada de lo que ya tengas montado, pero suma una capa más por fuera de la infraestructura y te baja las probabilidades de acabar dando explicaciones. Por eso yo lo metería en cualquier estrategia de seguridad de red que se tome en serio.

Cosas básicas de las técnicas de fuzzing

El fuzzing testing consiste en proporcionar entradas válidas e inválidas al software para obtener respuestas. El objetivo es hacer que el software falle. Después ya veremos si es una vulnerabilidad o no.

masks

Los básicos del fuzzing son estos cuatro:

  1. Empiezas metiendo datos por los campos de entrada del programa, válidos e inválidos, ya sea a mano o con herramientas que lo hagan por ti.
  2. Luego mutas esos datos para sacar casos de prueba nuevos. Cambias el formato, la longitud o el contenido, y a ver qué sale.
  3. Todo esto se automatiza, porque la gracia está en lanzar miles de casos contra el software, no diez.
  4. Y mientras tanto vigilas lo que hace el programa: crashes, cuelgues, fugas de memoria, cualquier cosa que se salga de lo normal.

Buenas prácticas para el fuzzing

El fuzzing testing implica varios pasos, como:

fuzz testing

Fallos comunes que no debes cometer

common pitfalls

Sí, se me fue un poco la mano con esto de los dibujitos. Pero es que me encantan :)

Scapy y su relevancia en la seguridad de redes

scapy

Scapy es una herramienta escrita en Python que sirve para montar tráfico de red de muy bajo nivel sin pelearte con sockets ni con estructuras binarias. Con cuatro líneas te armas los paquetes que quieras y los mandas a ver qué pasa. Para hacer fuzzing va de lujo.

Fuzzing de protocolos usando Scapy

Una de las cosas para las que más se usa Scapy es justo esto, hacer fuzzing de protocolos de red. Lo primero es instalarlo con pip:

$ pip install scapy

Una vez instalado puedes usar Scapy para crear paquetes. Por ejemplo, para crear un paquete ICMP simple, puedes usar el siguiente código:

from scapy.all import *

ip = IP(dst="192.168.1.1")
icmp = ICMP()
packet = ip/icmp

response = sr1(packet, timeout=2, verbose=0)

if response:
    print(f"Source IP: {response[IP].src}")
    print(f"Source Port: {response[IP].sport}")
    
else:
    print("No response received")

Para realizar el fuzzing de protocolos, puedes modificar el paquete cambiando sus campos o añadiendo nuevos. Por ejemplo, para hacer fuzzing del campo tipo ICMP, puedes usar el siguiente código:

from scapy.all import *

ip = IP(dst="192.168.1.1")
icmp = ICMP(type=RandShort())  # Random ICMP type value
packet = ip/icmp

response = sr1(packet, timeout=2, verbose=0)

if response:
    print(f"Source IP: {response[IP].src}")
    print(f"Source Port: {response[IP].sport}")
    
else:
    print("No response received")

Aquí lo que hacemos es poner el campo tipo de ICMP a un valor aleatorio entre 0 y 255 con la función RandShort(). Manda un montón de paquetes con valores distintos y verás hasta dónde aguanta el protocolo y en qué punto empieza a comportarse de forma rara.

Conceptos de fuzzing: una forma pésima de hacer fuzzing y la correcta

En seguridad de redes, el fuzzing sirve para medir cuánto aguanta un protocolo antes de romperse. Le metes al sistema un montón de datos mal formados, inesperados o directamente aleatorios, y esperas a que haga algo que no debería: un crash, un cuelgue, lo que sea. La idea es encontrar tú esos fallos en el software, los servicios o los dispositivos antes de que los encuentre alguien con peores intenciones.

Y aquí es donde la gente se divide en dos grupos. Están los que mandan paquetes arbitrarios contra un sistema sin estructura ni objetivo, a ver si suena la flauta. Eso casi nunca encuentra nada y encima te llena el informe de falsos positivos. Y luego está hacerlo bien, que consiste en entender el protocolo que estás probando y construir los paquetes apuntando a los sitios donde puede haber fallos. Con lo segundo sacas debilidades reales e información que sirve para algo.

Y si quieres hacerlo bien de verdad, en lugar de hacer chapuzas, hay una parte que no te puedes saltar: interpretar los resultados.

Ejemplos de scripts de fuzzing simples y cómo interpretar sus resultados

Aquí tienes un ejemplo de un script de fuzzing simple usando Scapy.

from scapy.all import *

# Define the target IP address and port number
target_ip = "192.168.1.1"
target_port = 80

# Define the number of packets to send
num_packets = 100

# Define the fuzzing function
def fuzz(packet):
    # Modify the payload of the packet with random data
    payload = "A" * (packet.len - 28)
    return IP(dst=target_ip)/TCP(sport=RandShort(), dport=target_port)/payload

# Send the fuzzed packets
for i in range(num_packets):
    packet = IP(dst=target_ip)/TCP(sport=RandShort(), dport=target_port)
    fuzzed_packet = fuzz(packet)
    send(fuzzed_packet)

# Analyze the results
answered_packets = sr1(IP(dst=target_ip)/TCP(dport=target_port), timeout=5, verbose=0)
if answered_packets:
    print(f"Received response from {answered_packets[IP].src}:{answered_packets[TCP].sport}")
else:
    print("No response received")

El script manda varios paquetes TCP contra una IP y un puerto objetivo con una carga útil aleatoria. sr1() se queda esperando la respuesta del objetivo, si es que llega alguna, y en ese caso imprime la IP y el puerto de origen.

Para leer el resultado, fíjate en cualquier cosa que se salga de lo esperado: crashes, cuelgues, respuestas que no encajan. Y si no contesta nada, ojo, porque puede significar dos cosas muy distintas: que el objetivo se ha quedado frito con tus paquetes, o que tus paquetes no están saliendo bien. Antes de cantar victoria, comprueba lo segundo.