on

Detección de vulnerabilidades en servicios de red mediante fuzzing – Parte I

Una de las técnicas más utilizadas para la búsqueda de vulnerabilidades es el fuzzing. Consiste en probar, de forma más o menos inteligente, el comportamiento de una aplicación frente a unos datos generados específicamente para hacer que un programa falle, ya sea generando datos en una codificación diferente, enviando cadenas largas o probando a desbordar valores numéricos.

Una de las partes más importantes del fuzzing es la automatización y la instrumentalización. El analista busca generar una batería de pruebas que pueden tardar horas o días en realizarse, por lo que las aplicaciones o frameworks destinados a este tipo de trabajos, suelen incorporar monitores para detectar la caída de la aplicación que se esté probando. Además, permiten grabar la sesión y datos relativos a la caída, como volcado de memoria, captura de red o desensamblado de la función afectada.

Sulley (http://code.google.com/p/sulley/) es una de esas herramientas que posibilitan generar un fuzzer específico para cada aplicación. Está programada en python por el creador de Paimei (Pedram Amini) y es mantenida con regularidad. Su principal cometido es servir de herramienta para escribir fuzzers basados en red. Tiene una interfaz web sencilla que nos dará información sobre el estado de la sesión y desde ella, se podrá acceder a la información referente a los “crashdumps” que haya obtenido de los agentes. Éstos capturan el tráfico de red y el estado del proceso para cada test donde se haya detectado un comportamiento anómalo.

Comenzando la sesión

Partamos de un ejemplo sencillo para fuzzear un servidor web:

[sourcecode lang=”python”]
from sulley import *

# Inicializamos el bloque de datos
s_initialize(“HTTP Simple Request Fuzz”)

# Definición “GET[espacio]/petición[espacio]HTTP/1.0[nueva línea][nueva línea]”
s_static(“GET”)
s_delim(” “)
s_delim(“/”)
s_string(“peticion”)
s_static(” HTTP/1.0″)
s_static(“rnrn”)

sess = sessions.session()
target = sessions.target(“127.0.0.1”,80)
sess.add_target(target)
sess.connect(s_get(“HTTP Simple Request Fuzz”))
sess.fuzz()
[/sourcecode]

El primer paso es definir el nombre del bloque con s_initialize:

[sourcecode lang=”python”]
s_initialize(“HTTP Simple Request Fuzz”)
[/sourcecode]

Desde aquí y hasta la siguiente llamada a s_initialize, definirá modelos de datos. Luego, dependiendo del tipo de dato, elegirá la forma más lógica de fuzzearlo. Acto seguido, se define la petición que Sulley enviará al servidor. En este caso, sólo nos interesa fuzzear los delimitadores y la petición:

[sourcecode lang=”python”]
s_static(“GET”)
s_delim(” “)
s_delim(“/”)
s_string(“peticion”)
s_static(” HTTP/1.0″)
s_static(“rnrn”)
[/sourcecode]

Sulley nos provee de un conjunto de primitivas suficientes para definir un protocolo tanto textual como binario. La lista de primitivas es la siguiente:

  • s_binary: Acepta valores binario en varios formatos (0xc0 0xff ee ea x00 fe 00 01 02 0xc50xc2 f0 0d).
  • s_static: Valor que no cambia.
  • s_dunno, s_raw and s_unknown: alias de s_static.
  • s_byte, s_char: 1 byte.
  • s_word, s_short: 2 bytes.
  • s_dword, s_long, s_int: 4 bytes.
  • s_qword, s_double: 8 bytes.
  • s_string: Cadena de texto.
  • s_delim: Delimitador.
  • s_random: Cadena generada aleatoriamente.

También se pueden definir tipos de datos más complejos con los legos e incluso la codificación de los datos resultantes. Pero para el ejemplo que nos aborda, bastará con los elementos más básicos. Con el protocolo definido, pasemos a la sesión. Cada sesión puede ser guardada en un fichero serializado para tener la posibilidad de pausar el proceso y retomarlo. También creamos el objeto que albergará la información del objetivo:

[sourcecode lang=”python”]
sess = sessions.session()
target = sessions.target(“127.0.0.1”,80)
sess.add_target(target)
sess.connect(s_get(“HTTP Simple Request Fuzz”))
sess.fuzz()
[/sourcecode]

Dentro de target, se indicará el host y el puerto al que deberá conectar Sulley para las pruebas. Aquí también deberíamos definir las opciones de los monitores, aunque no se aplique a este caso. El objetivo se añade a la sesión (sess.add_target(target)) y creamos el árbol de pruebas. Internamente, Sulley mantiene un grafo con cada una de las fases por las que tiene que pasar:

[sourcecode lang=”python”]
sess.connect(s_get(“sess.connect(s_get(“HTTP Simple Request Fuzz”))”))
[/sourcecode]

Con esto ya estamos preparados para lanzar el proceso. Esta última línea, crea un nuevo nodo en el arbol (previamente definimos “HTTP Simple Request Fuzz” con la llamada a s_initialize). Para terminar, la llamada sess.fuzz() comienza le proceso:

Simple HTTP Fuzz funcionando

En este otro ejemplo, se usará Sulley para descubrir una vulnerabilidad conocida en Easy Ftp Server (http://easyftpsvr.googlecode.com/):

[sourcecode lang=”python”]
#!/usr/bin/env python
from sulley import *

def recv_ftp(sock):
sock.recv(1024)

s_initialize(“user”)
s_static(“USER”)
s_delim(” “)
s_static(“anonymous”)
s_static(“rn”)

s_initialize(“pass”)
s_static(“PASS”)
s_delim(” “)
s_static(“anonymous@anon.com”)
s_static(“rn”)

s_initialize(“dele”)
s_static(“DELE”)
s_delim(” “)
s_string(“AAAA”)
s_static(“rn”)

s_initialize(“stor”)
s_static(“STOR”)
s_delim(” “)
s_string(“AAAA”)
s_static(“rn”)

print “Simple FTP Fuzzer (DELE,STOR)”

sess = sessions.session(session_filename=”ftp.session”, sleep_time=0.25)

target = sessions.target(“172.16.32.128”, 28)
target.procmon_options = {
“proc_name” : “ftpbasicsvr.exe”,
“stop_commands” : [‘wmic process where (name=”ftpbasicsvr.exe”) delete”‘],
“start_commands” : [‘”C:\Documents and Settings\Administrator\Desktop\easyftp-server-1.7.0.11-en\ftpbasicsvr.exe”‘],
}

target.netmon = pedrpc.client(“172.16.32.128”, 26001)
target.procmon = pedrpc.client(“172.16.32.128”, 26002)

sess.pre_send = recv_ftp # Leemos la respuesta
sess.add_target(target)

# Inicio de la autenticacion
sess.connect(s_get(“user”))
sess.connect(s_get(“user”),s_get(“pass”))

# Pasamos del estado autenticado al comando
sess.connect(s_get(“pass”),s_get(“stor”))
sess.connect(s_get(“pass”),s_get(“dele”))

fh = open(“ftp.udg”, “w+”)
fh.write(sess.render_graph_udraw())
fh.close()

print “Fuzzing…”
sess.fuzz()

[/sourcecode]

En el ejemplo, se definen 4 bloques distintos (user,pass,dele y stor). Los dos primeros se emplean para que Sulley se autentique y los otros dos serán los que realmente nos interesa probar. En este caso sí interesa guardar la sesión y se define un nuevo parámetro sleep_time para espaciar cada petición. Para monitorizar el proceso, se han ejecutado en la máquina destino dos agentes (network_monitor y process_monitor):

Monitores ejecutándose en la máquina destino

Monitores ejecutándose en la máquina destino

En el caso de network_monitor, se indica la interfaz en la que tiene que escuchar (-d 1), el filtro (-f “port 28”) y la ruta donde almacenará las capturas de red. Para el agente process_monitor, el nombre del fichero que contendrá el “crash” (-c auditsftp.crash) y el nombre del proceso (-p “ftpbasicsvr.exe”). Sulley se comunicará con los dos agentes a través del protocolo pedrpc para conocer el resultado de cada test. Por su parte, process_monitor ejecutará las órdenes que se pasarán desde el script cuando se detecte la caída del proceso:

[sourcecode lang=”python”]
target = sessions.target(“172.16.32.128”, 28)
target.procmon_options = {
“proc_name” : “ftpbasicsvr.exe”,
“stop_commands” : [‘wmic process where (name=”ftpbasicsvr.exe”) delete”‘],
“start_commands” : [‘”C:\Documents and Settings\Administrator\Desktop\easyftp-server-1.7.0.11-en\ftpbasicsvr.exe”‘],
}

target.netmon = pedrpc.client(“172.16.32.128”, 26001) # Puerto 26001 por defecto
target.procmon = pedrpc.client(“172.16.32.128″, 26002) # Puerto 26002 por defecto
[/sourcecode]

Antes de empezar, enseñamos a Sulley cómo tiene que actuar:

[sourcecode lang=”python”]
# Inicio de la autenticacion
sess.connect(s_get(“user”))
sess.connect(s_get(“user”),s_get(“pass”))

# Pasamos del estado autenticado al comando
sess.connect(s_get(“pass”),s_get(“stor”))
sess.connect(s_get(“pass”),s_get(“dele”))
[/sourcecode]

La parte novedosa con respecto al primer ejemplo es la dependencia entre bloques. Primero Sulley tiene que enviar el usuario (bloque “user”) y para enviar la contraseña, tendrá que pasar primero por la fase del usuario (bloque “pass”). Lo mismo para con “stor” y “dele”, que se ejecutarán sólo cuando los bloques dependientes hayan sido fuzzeados.

Para verlo más claro, es posible volcar el grafo a disco en varios formatos (sess.render_grap_udraw()). Después de convertirlo a png quedaría de la siguiente manera:

Gráfico de dependencias generado con UDraw

Gráfico de dependencias generado con UDraw

Después de unos minutos, se empiezan a ver los resultados:

Consola web de Sulley

Consola web de Sulley

La consola web muestra el progreso de la sesión y dos errores detectados. Clickando sobre cada uno de los enlaces, mostrará el volcado del proceso que estábamos auditando en el momento del fallo. También tendremos la captura de red con el paquete que hizo saltar el error. Mediante técnica de fuzzing, habremos conseguido encontrar una vulnerabilidad en la aplicación. A partir de este punto, tocará estudiar el dump y reproducir la situación con algún debugger para tracear el error y escribir el exploit.

En la próxima entrega, profundizaremos un poco más en Sulley, desgranando algún elemento más que ayudará en las sesiones de fuzzing.

Un saludo.

Demo Free Trial MSSP
Program