Descubrimiento de vulnerabilidades en bibliotecas de código abierto Parte 1: Herramientas del oficio


Resumen Ejecutivo

El código abierto se ha convertido en la base del desarrollo de software moderno. Los proveedores utilizan software de código abierto para seguir siendo competitivos y mejorar la velocidad, la calidad y el costo del proceso de desarrollo. Al mismo tiempo, es fundamental mantener y auditar las bibliotecas de código abierto utilizadas en los productos, ya que pueden exponer un volumen significativo de riesgo.

La responsabilidad de auditar el código por posibles riesgos de seguridad recae en la organización que lo utiliza. Hemos visto uno de los vulnerabilidades de mayor impacto se originan con software de código abierto en el pasado. La famosa violación de datos de Equifax se debió a una vulnerabilidad en el componente de código abierto Apache Struts, ampliamente utilizado en los marcos web convencionales. Además, el Informe de análisis y riesgos de seguridad de código abierto 2020 afirma que de las aplicaciones auditadas en 2019, el 99% de las bases de código contenían componentes de código abierto y el 75% de las bases de código contenían vulnerabilidades, y el 49% de las bases de código contenían vulnerabilidades de alto riesgo.

Las bibliotecas gráficas tienen una historia rica de vulnerabilidades y el volumen de problemas explotables se magnifican especialmente cuando el código base es relativamente más antiguo y no se ha recompilado recientemente. Resulta que las bibliotecas de gráficos en Linux se utilizan ampliamente en muchas aplicaciones, pero no están suficientemente auditadas y probadas por problemas de seguridad. Esto eventualmente se convirtió en una fuerza impulsora para que probamos múltiples gráficos vectoriales y bibliotecas GDI en Linux, una de las cuales fue libEMF, una biblioteca C ++ de Linux escrita para un propósito similar y utilizada en múltiples herramientas de gráficos que admiten la conversión de gráficos a otros formatos vectoriales. Probamos esta biblioteca durante varios días y encontramos múltiples vulnerabilidades, que van desde múltiples problemas de denegación de servicio, desbordamiento de enteros, acceso a memoria fuera de los límites, condiciones de uso después de la liberación y uso de memoria no inicializada.

Todas las vulnerabilidades eran explotables localmente. Les informamos al responsable del código, lo que llevó a que se lanzaran dos nuevas versiones de la biblioteca en cuestión de semanas. Esto refleja el compromiso de McAfee de proteger a sus clientes de las próximas amenazas de seguridad, incluida su defensa contra las que se encuentran en el software de código abierto. Gracias a la colaboración con los investigadores de McAfee, todos los problemas de esta biblioteca se solucionaron de manera oportuna.

En este blog enfatizaremos por qué es fundamental auditar el código de terceros que usamos a menudo en nuestros productos y describiremos las prácticas generales para que los investigadores de seguridad lo prueben en busca de problemas de seguridad.

Introducción

Fuzzing es una técnica extremadamente popular utilizada por los investigadores de seguridad para descubrir posibles vulnerabilidades de día cero en software de código abierto y cerrado. Aceptado como un proceso fundamental en las pruebas de productos, muchas organizaciones lo emplean para descubrir vulnerabilidades antes en el ciclo de vida del desarrollo del producto. Al mismo tiempo, se pasa por alto sustancialmente. Un fuzzer bien diseñado generalmente se compone de un conjunto de herramientas para hacer que el proceso de fuzzing sea relativamente más eficiente y lo suficientemente rápido como para descubrir errores explotables en un período corto, lo que ayuda a los desarrolladores a corregirlos temprano.

Varios de los fuzzers disponibles en la actualidad ayudan a los investigadores a guiar el proceso de fuzzing midiendo la cobertura del código, utilizando técnicas de instrumentación de código estático o dinámico. Esto eventualmente da como resultado entradas más eficientes y relevantes para el software de destino, ejercitando más rutas de código, lo que lleva a más vulnerabilidades descubiertas en el destino. Los marcos de fuzzing modernos también vienen con canales impulsados ​​por retroalimentación para maximizar la cobertura de código del software de destino, al aprender el formato de entrada a lo largo del camino y comparar la cobertura de código de la entrada a través de mecanismos de retroalimentación, lo que resulta en entradas mutadas más eficientes. Algunos de los marcos de fuzzing de última generación disponibles son American Fuzzy Lop (AFL), LibFuzzer y HongFuzz.

Fuzzers como AFL en Linux vienen con envoltorios de compilador (afl-gcc, afl-clang, etc.). Con el módulo de análisis de ensamblado afl-as, AFL analiza el código ensamblador generado para agregar instrumentación en tiempo de compilación, lo que ayuda a visualizar la cobertura del código. Además, los compiladores modernos vienen con desinfectante módulos como Address Sanitizers (ASAN), Memory Sanitizers (MSAN), Leak Sanitizers (LSAN), Thread Sanitizers (TSAN), etc., que pueden aumentar aún más las capacidades de detección de errores del fuzzer. A continuación, se destaca la variedad de errores de corrupción de la memoria que los desinfectantes pueden descubrir cuando se usan con difusores.

ASAN MSAN UBSAN TSAN LSAN
Usar después de las vulnerabilidades libres Lecturas de memoria no inicializadas Desreferencias de puntero nulo Condiciones de carrera Detección de fugas de memoria en tiempo de ejecución
Desbordamiento del búfer de montón Desbordamientos de enteros firmados
Desbordamiento del búfer de pila Desbordamientos encasillados
Errores de orden de inicialización Dividir por cero errores
Pérdidas de memoria
Acceso fuera de límites

Uno de los objetivos del equipo de investigación de vulnerabilidades de McAfee es difundir múltiples bibliotecas de código abierto y cerrado e informar las vulnerabilidades a los proveedores antes de que sean explotadas. En las próximas secciones de este blog, nuestro objetivo es resaltar las vulnerabilidades que descubrimos e informamos al investigar una biblioteca de código abierto, LibEMF (ECMA-234 Metafile Library).

Uso de American Fuzzy Lop (AFL)

Gran parte de los detalles técnicos y el funcionamiento de este fuzzer impulsado por retroalimentación de última generación está disponible en su documentación. Si bien AFL tiene muchos casos de uso, el más común es difuminar programas escritos en C / C ++ ya que son susceptibles a errores de corrupción de memoria ampliamente explotados, y ahí es donde AFL y sus estrategias de mutación son extremadamente útiles. AFL dio lugar a varias bifurcaciones como AFLSmart , AFLFast y Python AFL, difiriendo en sus estrategias de mutación y extensiones para aumentar el rendimiento. Finalmente, AFL también se importó a la plataforma Windows, WinAFL, utilizando un enfoque de instrumentación dinámica predominantemente para fuzzing binario de código cerrado.

El proceso de fuzzing comprende principalmente las siguientes tareas:

Fuzzing libEMF (biblioteca de metarchivos ECMA-234) con AFL

LibEMF (Enhanced Metafile Library) es una biblioteca de análisis de EMF escrita en C / C ++ y proporciona un juego de herramientas de dibujo basado en ECMA-234. El propósito de esta biblioteca es crear archivos de gráficos vectoriales. La documentación de esta biblioteca está disponible aquí y es mantenido por el desarrollador.

Elegimos fuzz este LibEMF con AFL fuzzer debido a sus capacidades de instrumentación de tiempo de compilación y buenas estrategias de mutación como se mencionó anteriormente. Tenemos el código fuente compilado en modo reforzado, que agregará opciones de endurecimiento de código mientras se invoca el compilador posterior, lo que ayuda a descubrir errores de corrupción de memoria.

Compilando la fuente

Para usar las capacidades de instrumentación de código de AFL, debemos compilar el código fuente con el contenedor del compilador AFL afl-gcc / afl-g ++ y, con un indicador de desinfección de direcciones adicional habilitado, usar el siguiente comando:

./configure CXX = afl-g ++ CFLAGS = ”- fsanitize = dirección -ggdb” CXXFLAGS = ”- fsanitize = dirección -ggdb” LDFLAGS = ”- fsanitize = dirección”

A continuación se muestra una instantánea del proceso de compilación que muestra cómo se agrega la instrumentación al código:

Paquete python de pwntools viene con un buen script de utilidad, checksec, que puede examinar las propiedades de seguridad binaria. La ejecución de checksec sobre la biblioteca confirma que el código ahora está instrumentado en ASAN. Esto también nos permitirá descubrir errores de acceso a la memoria que no se bloquean:

El arnés de prueba es un programa que utilizará las API de la biblioteca para analizar el archivo proporcionado al programa como argumento de la línea de comandos. AFL utilizará este arnés para pasar sus archivos mutados como argumento a este programa, lo que dará como resultado varias ejecuciones por segundo. Mientras escribe el arnés, es extremadamente importante liberar los recursos antes de regresar para evitar un uso excesivo que eventualmente puede bloquear el sistema. Aquí se muestra nuestro arnés para analizar archivos EMF usando API de la biblioteca libEMF:

AFL también rastreará la cobertura del código con cada entrada que pase al programa y, si las mutaciones dan como resultado nuevos estados del programa, agregará el caso de prueba a la cola. Compilamos nuestro arnés de prueba usando el siguiente comando:

afl-g ++ -o playemffile playemffile.c -g -O2 -D_FORTIFY_SOURCE = 0 -fsanitize = dirección -I / usr / local / include / libEMF / -L / usr / local / lib / libEMF -lEMF

Recopilación de casos de prueba

Si bien un fuzzer puede aprender y generar el formato de entrada incluso a partir de un archivo semilla vacío, la recopilación del corpus inicial de los archivos de entrada es un paso importante en un proceso de fuzzing eficaz y puede ahorrar grandes cantidades de ciclos de CPU. Dependiendo de la popularidad del formato de archivo, rastrear la web y descargar el conjunto inicial de archivos de entrada es uno de los enfoques más intuitivos. En este caso, no es una mala idea construir manualmente los archivos de entrada con una variedad de estructuras de registro EMF, utilizando bibliotecas de generación de archivos de gráficos vectoriales o API de Windows GDI. Pyemf es una de esas bibliotecas disponibles con enlaces de Python que se pueden usar para generar archivos EMF. A continuación, se muestra un código de ejemplo para generar un archivo EMF con un registro EMR_EXTEXTOUTW utilizando las API de Windows. La construcción de estos archivos con los diferentes registros EMR asegurará archivos de entrada funcionalmente diferentes, ejercitando diferentes manejadores de registros en el código.

Ejecutando el Fuzzer

Ejecutar el fuzzer es simplemente ejecutar el afl-fuzz comando con los parámetros como se muestra a continuación. Necesitaríamos proporcionar el corpus de entrada de los archivos EMF (-i EMFs /), el directorio de salida (-o output /) y la ruta al binario del arnés con @@, lo que significa que el fuzzer pasará el archivo como un argumento al binario . También necesitamos usar -m ninguno ya que el binario instrumentado ASAN necesita una gran cantidad de memoria.

afl-fuzz -m ninguno -i EMFs / -o salida / – /home/targets/libemf-1.0.11/tests/playemffile @@

Sin embargo, podemos realizar varios ajustes en la instancia de AFL en ejecución para aumentar el número de ejecuciones por segundo. AFL proporciona un modo persistente que es fuzzing en la memoria. Esto evita bifurcar un nuevo proceso en cada ejecución, lo que aumenta la velocidad. También podemos ejecutar varias instancias de AFL, una en cada núcleo, para aumentar la velocidad. Más allá de esto, AFL también proporciona una herramienta de minimización del tamaño de archivo que se puede utilizar para minimizar el tamaño del caso de prueba. Aplicamos algunos de estos trucos de optimización y, como podemos ver a continuación, hay un aumento dramático en la velocidad de ejecución alcanzando ~ 500 ejecuciones por segundo.

Después de aproximadamente 3 días de filtrar esta biblioteca, tuvimos más de 200 bloqueos únicos, y cuando los evaluamos notamos 5 bloqueos únicos. Informamos de estos fallos al desarrollador de la biblioteca junto con MITRE y, tras ser reconocidos, CVE-2020-11863, CVE-2020-11864, CVE-2020-11865, CVE-2020-11866 y CVE-2020-13999 fueron asignados a estas vulnerabilidades. A continuación, analizamos nuestros hallazgos para algunas de estas vulnerabilidades.

CVE-2020-11865: acceso a memoria fuera de los límites al enumerar los objetos de stock EMF

Al clasificar uno de los bloqueos producidos por el fuzzer, vimos SIGSEGV (violación de acceso a la memoria) para uno de los archivos EMF proporcionados como entrada. Cuando el binario se compila con los símbolos de depuración habilitados, ASAN usa LLVM Symbolizer para producir los rastros de pila simbolizados. Como se muestra a continuación, ASAN genera el seguimiento de la pila que ayuda a profundizar más en este bloqueo.

Mirar el punto de falla en el desmontaje indica claramente el acceso a la memoria fuera de los límites en la función GLOBALOBJECTS :: find.

Al analizar más a fondo este bloqueo, resultó que la vulnerabilidad estaba en el acceso al vector de objetos global que tenía punteros para almacenar objetos. Los objetos de stock son principalmente objetos gráficos lógicos que se pueden utilizar en operaciones gráficas. Cada uno de los objetos de stock utilizados para realizar operaciones gráficas tiene su conjunto de bits de orden superior, como se muestra a continuación en la documentación de MS. Durante el procesamiento del metarchivo, el índice del objeto de stock relevante se puede determinar enmascarando el bit de orden superior y luego usando ese índice para acceder al puntero al objeto de stock. El código de procesamiento de metarchivo intenta recuperar el puntero del vector de objeto global al intentar acceder al índice después de enmascarar el bit de orden superior, como se ve justo encima de la instrucción de punto de falla, pero no verifica el tamaño del vector de objeto global antes de acceder al índice , lo que conduce a un acceso vectorial fuera de los límites mientras se procesa un archivo EMF diseñado.

A continuación se muestra el código vulnerable y fijo donde se agregó la verificación del tamaño del vector:

CVE-2020-13999 – Desbordamiento de entero firmado mientras se procesa el registro EMR_SCALEVIEWPORTEX

Otra falla en el código que evaluamos resultó ser una condición de desbordamiento de entero con signo al procesar un registro EMR_SCALEVIEWPORTEXT en el metarchivo. Este registro especifica la ventana gráfica en el contexto actual del dispositivo y se calcula calculando las proporciones. Un registro EMR_SCALEVIEWPORTEXTEX se ve así, según la especificación del registro. Una nueva ventana gráfica se calcula como se muestra a continuación:

Como parte de la estrategia de mutación binaria de AFL, aplica un enfoque determinista en el que ciertos conjuntos de números enteros codificados reemplazan los datos existentes. Algunos de estos son MAX_INT, MAX_INT-1, MIN_INT, etc., lo que aumenta la probabilidad de activar condiciones de borde mientras la aplicación procesa datos binarios. A continuación se muestra una de esas mutaciones realizada por AFL en la estructura de registro EMF:

Esto resultó en el siguiente accidente mientras se realizaba la operación de división.

A continuación, vemos cómo esta condición, que eventualmente condujo a una denegación de servicio, se solucionó agregando verificaciones de desbordamiento de división en el código:

CVE-2020-11864: pérdidas de memoria al procesar varios registros de metarchivo

Leak Sanitizer (LSAN) es otra herramienta importante que está integrada con la ASAN y se puede utilizar para detectar fugas de memoria en tiempo de ejecución. LSAN también se puede utilizar en modo autónomo sin ASAN. Al clasificar los bloqueos generados, notamos varias fugas de memoria al procesar múltiples estructuras de registro EMF. Uno de ellos es el que se muestra a continuación mientras se procesa el registro de metarchivo EXTTEXTOUTA, que luego se corrigió en el código liberando el búfer de memoria cuando hay excepciones al leer los metarchivos corruptos.

Aparentemente, las pérdidas de memoria pueden llevar a un uso excesivo de recursos en el sistema cuando la memoria no se libera después de que ya no se necesita. Esto eventualmente conduce a la denegación de servicio. Encontramos problemas de pérdida de memoria mientras libEMF procesaba varios de esos registros de metarchivo. La misma naturaleza de corrección, liberando el búfer de memoria, se aplicó a todo el código de procesamiento vulnerable:

Además, también informamos sobre múltiples condiciones de uso después de la ausencia y problemas de denegación de servicio que finalmente se solucionaron en la versión más reciente de la biblioteca lanzada. aquí.

Conclusión

Fuzzing es un proceso importante y fundamental para probar la calidad de un producto de software. El proceso se vuelve crítico, especialmente cuando se utilizan bibliotecas de terceros en un producto que puede tener vulnerabilidades explotables. Auditarlos por problemas de seguridad es crucial. Creemos que las vulnerabilidades que informamos son solo la punta del iceberg. Hay varias bibliotecas heredadas que probablemente requieran una auditoría exhaustiva. Nuestra investigación continúa con varias otras bibliotecas de Windows y Linux similares y continuaremos informando vulnerabilidades a través de nuestro proceso de divulgación coordinado. Creemos que esto también destaca que es fundamental mantener un buen nivel de colaboración entre los investigadores de vulnerabilidades y la comunidad de código abierto para informar y solucionar estos problemas de manera oportuna. Además, los compiladores modernos vienen con múltiples herramientas de instrumentación de código que pueden ayudar a detectar una variedad de errores de corrupción de memoria cuando se usan al principio del ciclo de desarrollo. Se recomienda utilizar estas herramientas cuando se audita el código en busca de vulnerabilidades de seguridad.





Enlace a la noticia original