En esta primera parte de esta pequeña saga que voy a realizar paralelamente a mi proyecto final de carrera, voy a realizar una breve introducción de la técnica del fuzzing.
Ésta técnica fue desarrollada en 1989 por el catedrático Barton Miller de la Universidad de Wisconsin Madison y sus estudiantes, que desarrollaron el primer fuzzer. En la actualidad podemos encontrar gran cantidad de este tipo de herramientas enfocadas a este tipo de testeo: Cfuzzer, fuzzled, fuzzer.pl, JBroFuzz, WebScarab, wapiti , Socket Fuzzer….
Una gran parte de las empresas dedicadas a desarrollar software lanzan sus productos con bugs de seguridad. Esto no es ningún secreto. Estas empresas son conscientes del gasto que implica, ya que posteriormente deben proporcionar parches para solucionar estas vulnerabilidades de seguridad, que se podrían mitigar haciendo una mayor inversión de presupuesto en sus primeras etapas de desarrollo (aunque nunca, como sabemos, se podrían eliminar del todo).
Una de las formas de localizar este tipo de vulnerabilidades, seria realizar ingeniería inversa y enviar datos a un punto de entrada del software a auditar, para ver la respuesta de éste. Sobra decir que esta es una tarea bastante tediosa. En la automatización de este proceso es donde entra el fuzzing.
El fuzzing no es otra cosa que una técnica para probar software. La idea es inyectar entradas de datos aleatorios en un programa. Si el programa falla (por ejemplo, se cuelga o nos responde con un error), entonces hay bugs que corregir. Por supuesto, aparte de errores funcionales, esta técnica permite descubrir vulnerabilidades del tipo format string, integer overflow, buffer overflow, inyecciones SQL…
Veamos algunos ejemplos de puntos de entrada de un programa:
- Archivos. En este caso el fuzzer genera un fichero con un formato que incluye partes mal formadas, para ver cómo son gestionadas por la aplicación.
- Periféricos: teclado, ratón, etc.
- Variables de entorno.
- Red. Aquí el fuzzer genera paquetes de red mal formados.
- Limitación de los recursos: memoria, disco duro, tiempo de CPU, etc.
- Etc.
En función de como se crean estas entradas a los programas, este tipo de herramientas se pueden categorizar en dos tipos: basados en la mutación y basados en la generación.
A. Mutación.
Como bien indica su nombre, este tipo de fuzzer mutará el tipo de entradas de la aplicación. Podríamos decir que los fuzzer basados en este método trabajan a ciegas, es decir, realizando una inyección de datos a las entradas de la aplicación, debido a la falta de comprensión del formato o la estructura de los datos. Este tipo de entradas no tienen una estructura definida ni necesitamos especificarla. En pocas palabras, se podría decir que le estamos pasando basura al programa y vemos si se cuelga o no.
Esto puede traer problemas, ya que el programa puede tener ciertos filtros básicos que nos indicará que la información introducida no tiene un formato correcto, o simplemente no tendrá los datos introducidos en cuenta.
Un ejemplo muy común sería el del protocolo HTTP.
Petición esperada:
GET /index.html HTTP/1.0
Con un script podríamos aumentar la repetición de algunos carácteres e incluso probar varias combinaciones:
GETTTTTTTTTT /index.html HTTP/1.0
GEEEEET /index.html HTTP/1.0
GET /////index.html HTTP/1.0
Este tipo de fuzzer es el más sencillo, ya que se utilizan diccionarios y plantillas para introducir los datos, pero el éxito esta limitado por el diccionario y las plantillas utilizadas. Otra desventaja de este método es la necesidad de una amplia muestra de ficheros base sobre los que hacer las mutaciones.
B. Generación.
En este modelo es necesario especificar cual es el formato válido de lo que vamos a fuzzear; no se trata de datos aleatorios. Por ejemplo, si vamos a generar un fichero mal formado con un formato concreto GIF debemos saber el formato correcto para que la aplicación lo acepte como entrada y no lo descarte de antemano.
A diferencia de los fuzzers de mutación, los de generación no modifican las entradas, sino que crean entradas nuevas sobre la base de un modelo de datos proporcionado por el creador del fuzzer. Este modelo es más lento que el de mutación, ya que requiere más conocimiento del formato de destino y la aplicación, y hay que definir toda la estructura,pero también es mayor la probabilidad de descubrir fallos que el otro método no tenía en cuenta, y podemos obtener mayor información de los crashes.
Si por ejemplo queremos realizar una prueba a una aplicación de de visualización de PDF, una vez entendido el formato de un archivo PDF, se crea un archivo desde cero, y se alteran los datos para introducir algunos. Por ejemplo, en este caso podríamos crear un documento PDF totalmente válido pero con unas dimensiones demasiado grandes, que hagan que la aplicación falle. No hubiese sido posible hacer esto si en lugar de un fichero que sigue correctamente la estructura PDF hubiésemos introducido un fichero aleatorio, que habría sido directamente descartado por la aplicación.
Y hasta aquí llega este breve introducción al fuzzing. En breve seguiremos con esta pequeña saga en la que entraremos más en detalle con los tipos, ejemplos y herramientas utilizadas.