Análisis del 0-day de WebP
(blog.isosceles.com)- La única corrección de seguridad incluida en la actualización estable de Chrome, CVE-2023-4863, es un desbordamiento de búfer en el heap de la biblioteca de imágenes WebP, y Google afirmó que ya existe un exploit en entornos reales
- El reporte del 6 de septiembre de 2023 de Apple SEAR, CVE-2023-41064 de Apple y el contexto de BLASTPASS de Citizen Lab encajan, por lo que es muy probable que se trate del mismo bug
- La vulnerabilidad se analiza como un problema en el proceso de construcción de la tabla Huffman de VP8L, la compresión sin pérdida de WebP, que permitía escribir más allá del tamaño de búfer precalculado
- La razón por la que el fuzzing general podía pasarla por alto fueron las condiciones de disparo complicadas, que exigían crear en orden varias tablas Huffman de tamaño máximo y una tabla inválida específica
- Aunque el parche upstream de libwebp parece suficiente, libwebp se usa ampliamente en navegadores, sistemas operativos y apps, por lo que la propagación de parches y el sandboxing son importantes
Por qué el parche de Chrome se conecta con BLASTPASS
- A principios de septiembre de 2023, Google corrigió en una actualización estable de Chrome CVE-2023-4863, reportada por Apple Security Engineering and Architecture (SEAR)
- La vulnerabilidad es un desbordamiento de búfer en el heap de la biblioteca de imágenes WebP
- Google afirmó que sabe que “existe un exploit para CVE-2023-4863 en entornos reales”
- En la misma época, Citizen Lab detectó actividad sospechosa en el iPhone de una persona perteneciente a una organización de la sociedad civil con sede en Washington DC
- BLASTPASS se vincula con un caso en el que se distribuyó el spyware Pegasus de NSO Group mediante un exploit zero-click zero-day de iMessage
- Después de que Citizen Lab enviara los resultados del análisis técnico a Apple, Apple publicó dos CVE en su aviso de seguridad del 7 de septiembre
- El primer CVE de Apple, CVE-2023-41061, coincide con indicios de que se empaquetó un exploit de imagen en un adjunto de PassKit para eludir el sandbox BlastDoor de iMessage
- La imagen maliciosa parece haber sido procesada en otro proceso sin sandbox
- El segundo CVE, CVE-2023-41064, es una vulnerabilidad de desbordamiento de búfer en Apple ImageIO
- ImageIO es el framework de Apple para parseo de imágenes, que identifica varios formatos de imagen y los conecta con el decodificador adecuado
- Sin detalles técnicos, no se confirmó qué formato de imagen afecta CVE-2023-41064
- Como Apple agregó recientemente soporte para WebP en ImageIO, el equipo de seguridad de Apple reportó a Chrome la vulnerabilidad de WebP el 6 de septiembre, y Google la parcheó de emergencia en 5 días y la marcó como explotada en la práctica, es muy probable que BLASTPASS y CVE-2023-4863 sean el mismo bug
La ubicación de la vulnerabilidad que señala el parche de libwebp
- Al comparar el ID de bug del aviso de seguridad de Chrome con los commits open source de libwebp, el parche Fix OOB write in BuildHuffmanTable corresponde a CVE-2023-4863
- El parche se creó el 7 de septiembre de 2023, al día siguiente del reporte de Apple
- La vulnerabilidad está en VP8L, el soporte de compresión sin pérdida de WebP
- La compresión sin pérdida de WebP usa Huffman coding para almacenar y restaurar píxeles con 100% de exactitud
- Las implementaciones modernas optimizan la decodificación usando tablas en lugar de una estructura conceptual de árbol
- La versión vulnerable asignaba memoria con un tamaño de búfer precalculado tomado de una tabla fija y luego construía directamente la tabla Huffman en ese espacio
- La nueva versión calcula en una primera pasada el tamaño total necesario para la tabla de salida, pero no escribe realmente
- Si el tamaño total es mayor que el búfer precalculado, ahora crea una asignación más grande
- El flujo clave es
huffman_tables, asignado enReadHuffmanCodesdesrc/dec/vp8l_dec.c, y las llamadas aVP8LBuildHuffmanTable/BuildHuffmanTabledentro deReadHuffmanCode- La tabla Huffman se divide en 5 segmentos con distintos tamaños de alfabeto
- Para provocar el desbordamiento, hay que construir con precisión las 5 tablas
El proceso para crear el archivo disparador
- La compresión sin pérdida de WebP asigna secuencias de bits cortas a valores frecuentes y secuencias de bits largas a valores raros, según el análisis de frecuencia de los píxeles de entrada
- Los códigos se construyen para que el decodificador siempre pueda distinguir la longitud de las secuencias de bits
- La imagen comprimida debe incluir información estadística y de asignación de códigos para reproducir el mapeo entre códigos y valores
- WebP también comprime la propia tabla Huffman con Huffman coding para reducir el tamaño del archivo
- Esta estructura vuelve complejo el proceso de analizar y disparar la vulnerabilidad
@mistymntncopproporcionó código de harness para crear un WebP de formato válido con datos de Huffman coding arbitrarios, es decir, code lengths- Con este harness fue posible pasar un arreglo arbitrario de
code_lengthsa la llamada objetivo deBuildHuffmanTable
- Con este harness fue posible pasar un arreglo arbitrario de
- En experimentos manuales, la interacción entre el histograma dentro de
BuildHuffmanTable,num_open,num_nodesy el valorkey, que rastrea la posición inicial deReplicateValue, resultó muy compleja- La root table de
count[0]acount[8]no afecta directamente en gran medida atotal_size, pero puede cambiar el estado interno posterior - Las second level tables de
count[9]acount[15]afectan directamente eltotal_sizefinal
- La root table de
- enough.c de Mark Adler imprime el histograma de la tabla de lookup máxima posible de un árbol Huffman para un tamaño de alfabeto, tamaño de root table y longitud máxima de código
- En un comentario se indica que
kTableSizede libwebp es el valor calculado con esta herramienta - Con esa herramienta se pudo reproducir el tamaño de búfer precalculado, y con la herramienta de
@mistymntncoptambién se confirmó que los code lengths generados por “enough” llenan al 100% la asignación dehuffman_tables
- En un comentario se indica que
Condiciones en las que ocurre el desbordamiento real
- La herramienta
enoughcalcula el máximo para códigos válidos y completos- El tamaño máximo de una de las tablas pequeñas, con tamaño de símbolo 40, root table de 8 bits y longitud máxima de código 15, es 410
- No se encontró ningún caso que produjera un tamaño mayor que 410 entre los códigos que
BuildHuffmanTableconsidera válidos
- El desbordamiento ocurre al poner un árbol Huffman inválido en la última etapa, más que solo con árboles Huffman válidos
- Primero se crean tablas de salida de tamaño máximo con 4 árboles Huffman válidos
- Si se pone un árbol Huffman inválido en la última tabla,
ReplicateValuepuede escribir fuera de rango antes de la verificación final de consistencia
- La reproducción consiste en hacer checkout del commit vulnerable de libwebp
7ba44f80f3b94fc0138db159afea770ef06532a0, activar AddressSanitizer, generarbad.webpcon el código PoC de@mistymntncopy decodificarlo condwebp- AddressSanitizer reporta
heap-buffer-overflowenBuildHuffmanTable - La escritura reportada ocurre en la dirección inmediatamente posterior a una región de 11816 bytes
- AddressSanitizer reporta
- Existen múltiples entradas que desbordan
huffman_tables- Entre los code lengths encontrados hay casos que escriben hasta 400 bytes después del final de la asignación de
huffman_tables - Aunque el control sobre el valor escrito sea parcial, parece explotable
- Entre los code lengths encontrados hay casos que escriben hasta 400 bytes después del final de la asignación de
- El árbol Huffman de la entrada inválida está parcialmente desbalanceado y contiene muchos nodos internos sin hijos en una sección de la rama desbalanceada
- Esta estructura crea un índice
keyque no puede alcanzarse con un árbol válido
- Esta estructura crea un índice
Cómo el parche evita el desbordamiento
- Al principio, el parche parecía una forma de evitar el desbordamiento de heap haciendo crecer dinámicamente el búfer según el tamaño necesario
- En realidad, la primera pasada de la versión parcheada ejecuta
BuildHuffmanTabley calcula el tamaño total necesario, pero no escribe en la tabla- La entrada inválida que causaba el desbordamiento hace que
BuildHuffmanTablefalle y devuelva 0 en esta primera pasada - Como la primera pasada no escribe, aunque el árbol inválido se procese parcialmente, no se produce escritura fuera de rango
- La entrada inválida que causaba el desbordamiento hace que
- Como no se encontró ningún caso de código válido y completo que causara el mismo desbordamiento, este parche parece suficiente
Por qué era fácil que el fuzzing lo pasara por alto
- libwebp ha sido sometido a fuzzing durante mucho tiempo en Google OSS-Fuzz, y el soporte sin pérdida de WebP también se estaba fuzzing ampliamente
- La dificultad central es que tanto el formato como las condiciones de disparo son complejos
- Entre miles de millones de posibilidades, primero hay que construir 4 tablas Huffman de tamaño máximo para tamaños de alfabeto 280 y 256
- Luego hay que crear una tabla Huffman inválida de una forma muy específica para un tamaño de alfabeto 40
- Si en cualquier etapa se equivoca apenas 1 bit, el decodificador de imágenes arroja un error y termina de forma segura
- Después de corregir el 0-day de WebP, Google lanzó un nuevo fuzzer dedicado a las rutinas Huffman de WebP
- Incluso al ejecutar ese fuzzer, no se encontró CVE-2023-4863
- Los enfoques estándar de mutaciones por bit flip, bucles de retroalimentación de cobertura de código y enfoques basados en entrada-estado como CmpLog de AFL++ también tienen dificultad para atravesar las etapas intermedias hasta llegar a ese estado extremo
- Técnicas de ejecución simbólica dinámica como TritonDSE de Quarkslab podrían tener posibilidades, pero no está confirmado
- Es diferente de la vulnerabilidad Load_SBit_Png de FreeType
- Load_SBit_Png no se descubrió porque el harness de fuzzing no reflejaba suficientemente cómo se usaba la API
- CVE-2023-4863 se parece más a un caso en el que el fuzzing era difícil por las restricciones de la propia vulnerabilidad, no por falta de harness
Alcance del impacto y estado de respuesta
- Citizen Lab capturó un exploit avanzado usado en entornos reales, y Apple y Chrome parecen haber distribuido actualizaciones a miles de millones de usuarios en cuestión de días
- En ese momento, Android todavía podía seguir potencialmente afectado
- BitmapFactory de Android procesa la decodificación de imágenes como Apple ImageIO y soporta libwebp
- El boletín de seguridad de Android de ese momento no incluía la corrección de CVE-2023-4863, pero la corrección ya se había integrado en AOSP
- Si Android estuviera afectado, podría derivar en exploits remotos en apps como Signal o WhatsApp
- Se esperaba que se corrigiera en el boletín de seguridad de octubre
- El parche upstream de libwebp parece haberse aplicado correctamente y se está propagando a donde hace falta
- Como libwebp se usa en muchos lugares, puede tomar tiempo hasta que el parche se distribuya lo suficiente
- En código de parsers complejos cercano a una superficie de ataque de exploits remotos zero-click, como la decodificación de imágenes, el fuzzing por sí solo no basta para garantizar la seguridad; se necesita invertir en revisión proactiva de código fuente y sandboxing adecuado
Asimetría en la divulgación de información técnica
- Cuando los proveedores no publican suficientes detalles técnicos, a los defensores les resulta difícil verificar el impacto de una vulnerabilidad
- Los atacantes tienen fuertes incentivos para rastrear y explotar vulnerabilidades N-day, y no se retrasan demasiado aunque no se publiquen los detalles
- Los defensores a menudo no tienen recursos suficientes para realizar este nivel de análisis técnico
- Si incluso se oculta información básica sobre el comportamiento del ataque, se crea una asimetría en la que los atacantes tienen más conocimiento que los defensores sobre la vulnerabilidad y el exploit
1 comentarios
Opiniones de Hacker News
Este bug se parece más que nada al bug de Timsort de 2015 [1]
Timsort es un ingenioso algoritmo híbrido de ordenamiento surgido de CPython, y varias implementaciones, incluida OpenJDK, lo adoptaron casi como una traducción a nivel de código fuente. Mantenía una pila de runs ordenados y había una prueba de que, por su estructura, existía una cota finita suficientemente pequeña para el tamaño máximo posible de la pila, pero la implementación original de CPython no coincidía exactamente con la prueba, por lo que en casos raros podía producirse un desbordamiento de pila. Por eso, en CPython era un bug de seguridad grave, pero Java lanzaba una excepción en ese caso, así que en OpenJDK no era un problema de seguridad del mismo tipo
Del mismo modo, este bug de WebP surgió porque el tamaño máximo de la tabla estaba probado formalmente, pero no coincidía con el valor que terminó en el código fuente real. Este tipo de bugs son difíciles de verificar y de revisar. Como hay una prueba y el código fuente también parece coincidir con ella, es fácil pensar que todo está bien. Parece una señal clara de que hacen mucha falta verificación formal accesible, más que revisión humana, y el uso de lenguajes con seguridad de memoria. Un sistema de tipos también puede verse como una forma débil de verificación formal
[1] http://envisage-project.eu/wp-content/uploads/2015/02/sortin...
https://github.com/google/wuffs
También se puede satisfacer este requisito escribiendo explícitamente las mismas comprobaciones, pero si creías que no hacían falta por tratarse de software de alto rendimiento, es muy probable que descubras que estabas equivocado justo cuando la herramienta de WUFFS no acepte el código. Es más débil que una verificación formal completa, pero para el objetivo de la seguridad del programa es una gran mejora y es mucho mejor que un “LGTM” humano
Además de “qué hay que parchear ahora”, hubo varias partes interesantes en este texto. Aunque se conozca la ubicación de la vulnerabilidad y la corrección, reproducir un PoC de exploit puede requerir bastante trabajo, y los descompresores sin pérdida dentro de los decodificadores de imágenes pueden ser bastante resistentes al fuzzing. Para la gente de seguridad tal vez sea obvio, pero como no especialista me resultó entretenido de leer
Hay algunas preguntas para las que no logro encontrar una respuesta clara. 1) ¿También se ven afectados otros navegadores basados en Chrome como Brave? 2) ¿También se ve afectado Chrome de escritorio, o es un problema solo móvil? 3) ¿Por qué nunca había oído hablar de WebP? Me pregunto si estuve desconectado del mundo o si es una tecnología mobile-first
Chrome de escritorio también estuvo afectado tanto en Linux como en Windows. Chrome incluye su propia libwebp, así que si Chrome está actualizado, aunque la distribución de Linux todavía no haya parcheado, al menos desde el punto de vista de ataques al navegador debería estar bien
Los principales navegadores y sistemas operativos soportan una cantidad sorprendente de formatos de imagen poco conocidos. Por ejemplo, en MacOS se puede cargar KTX2 (Khronos Texture Container), y en Android, DNG (Adobe Digital Negative). Hay muchas superficies de ataque interesantes y muy expuestas para que los atacantes exploren
El bug está en la biblioteca del códec, y WebP tiene un ecosistema prácticamente unificado en una sola implementación, así que todos usan la misma biblioteca y todos deben parchear
Google impulsó WebP hace 10 años, pero durante mucho tiempo fue exclusivo de Chrome, así que no ganó mucha tracción. Tampoco se estandarizó correctamente, aunque sí es open source. Comprime mejor que JPEG las imágenes de baja calidad, pero en imágenes de alta calidad tiende a producir colores corridos y borrosidad. Irónicamente, para cuando WebP empezó a tener soporte amplio, ya se estaba volviendo técnicamente obsoleto frente a AVIF y JPEG XL
Si un dispositivo Android ya no recibe soporte, ¿significa que en este momento está expuesto a un exploit de 0 clics que anda circulando en la práctica? ¿O basta con actualizar la app de SMS, Chrome, WhatsApp, Signal, etc. para bloquear suficientemente las principales vías de entrada? Me da curiosidad
En dispositivos sin soporte, actualizar Chrome corrige el problema en Chrome, pero para corregir apps como Signal o WhatsApp se necesita una actualización del sistema operativo Android. Chrome incluye su propia libwebp, pero apps muy expuestas, como las de mensajería y Gmail, usan interfaces provistas por el OS para mostrar imágenes. En los dispositivos Android que aún reciben soporte de seguridad, se espera que las actualizaciones empiecen a salir a principios de octubre
Lo de que “hay muchas entradas que efectivamente desbordan huffman_tables” parece un problema más generalizado. Es una estructura en la que el software A crea una tabla de consulta B, y esa tabla se usa para procesar un flujo de datos de entrada.
Ahora, cualquier desarrollador que se preocupe aunque sea un poco por la seguridad o la corrección debería garantizar una de dos cosas: A) que el software esté escrito de modo que ningún dato de entrada pueda hacer mal uso de la tabla de consulta ni hacerla fallar, o B) que solo se use en un entorno controlado, por ejemplo una comunicación punto a punto donde ambas partes sean de confianza, con la garantía de que el flujo nunca hará mal uso de la tabla de consulta ni provocará anomalías.
B es casi imposible fuera de un grupo u oficina pequeños, y a escala de Internet es directamente imposible, así que al final solo queda A. Como práctica recomendada generalizada para la ingeniería de software de aquí en adelante, si se usa una tabla de consulta por cualquier motivo, el desarrollador debe garantizar que esa tabla funcione correctamente con todos los tipos de datos, o que los datos inválidos se detecten y se manejen adecuadamente antes de llegar a la tabla.
Si fuera un investigador de seguridad serio y tuviera tiempo, reuniría una lista de todas las vulnerabilidades de seguridad históricas que hayan estado relacionadas de alguna forma con tablas de consulta, las leería una por una y compararía sus puntos en común. Probablemente haya un patrón. Luego revisaría todo el software que use tablas de consulta sobre flujos de datos y auditaría vulnerabilidades, pero eso no es algo que una sola persona pueda hacer en toda una vida: es un deporte de equipo
¿Cuánto habrá ayudado la publicación del código fuente a que NSO encontrara este bug? Me pregunto si, en caso de que el código hubiera sido solo un blob binario, los descompiladores modernos habrían sido suficientes para entenderlo y encontrar un bug tan rebuscado
Me sorprende que no sea una parte “nueva” del formato de imagen. La compresión Huffman existe desde hace más de 70 años, y Huffman canónico solo unas décadas menos; incluso JPEG usa Huffman. Es una tecnología de hace décadas y hay incontables textos sobre cómo implementarla, así que parecería una tecnología cuyos bugs ya deberían haberse limpiado en múltiples implementaciones.
Hace tiempo leí la especificación de JPEG y también escribí un decodificador, así que miré la especificación de WebP: https://developers.google.com/speed/webp/docs/webp_lossless_...
Lo importante está en la sección 6. Lo primero que llama la atención es que, a diferencia de la especificación de JPEG, no queda claro cómo se construyen los códigos. JPEG tiene muchos diagramas de flujo fáciles de leer sobre el proceso. WebP se parece más a LZH/deflate(zlib), y se siente menos como una “especificación” y más como una colección de fragmentos de código fuente con explicaciones.
Después de GIF, JPEG y PNG, estaba pensando en escribir también un decodificador WebP, pero viendo solo la “especificación” de arriba, casi se siente como si te dijera que no lo hagas
Las implementaciones de árboles de Huffman tienen muchos compromisos distintos, así que no creo que, por ser una tecnología vieja, todos los bugs ya estén resueltos. Charles Bloom dijo que el artículo determinante de 1997 sobre optimización de Huffman [1] todavía no era muy conocido en 2010, y que muchas optimizaciones se redescubrieron y luego se olvidaron. Por eso es muy probable que haya muchas implementaciones ineficientes.
[1] https://cbloomrants.blogspot.com/2010/08/08-12-10-lost-huffm...
Parece que ahora ni siquiera se puede ver imágenes sin preocuparse por la seguridad
Suena como los típicos dolores de crecimiento de usar C, un lenguaje inseguro. Entiendo que sea atractivo por la velocidad de los navegadores, pero ojalá la industria dejara de fomentar la repetición de los mismos errores que viene cometiendo desde que empezó a usar C.
Se siente como construir un puente con tornillos autorroscantes en vez de remaches porque es más rápido. Y cuando el puente empieza a combarse, la solución es meter más tornillos
“La buena noticia es que Apple y Chrome respondieron de forma excelente acorde con la urgencia de este problema”, dicen, pero cuesta aceptarlo. Fue Google quien clasificó este problema como exclusivo de Chrome. Solo en los últimos 7 días, todas las principales distribuciones de Linux tuvieron que impulsar actualizaciones, y Red Hat le dio una puntuación de 9.6. También se vieron afectadas imágenes Docker de Python con más de 1,000 millones de pulls, Puppeteer, WordPress, Node.js, etc., pero CRBug sigue privado.
Sitios como BleepingComputer informaron lo que veían sin investigar, y lo mismo hicieron muchas empresas de seguridad que trataron este issue como si fuera de un tercero. Cuando sabes que la otra parte no hizo la debida diligencia, es muy difícil generar confianza.
Adam Caudill escribió un buen artículo, “Whose CVE is it anyway?”[0], junto con el caso de 1Password, que parcheó temprano, y señala bien el problema del que se habla aquí. Citizen Lab se negó a responder si los dos issues estaban relacionados, pero no hace falta ser un genio para ver ciertas cosas.
[0]: https://adamcaudill.com/2023/09/14/whose-cve-is-it-anyway/
El autor también dice que muchos otros sistemas deben parchearse. Pero, de esas imágenes Docker de Python con 1,000 millones de pulls, ¿cuántas renderizan imágenes WebP no confiables? Lo mismo aplica a Node y otros. Claro que hay que parchear rápido, pero no están al mismo nivel que iOS/Android/Chrome.
Cientos de millones de personas en todo el mundo están afectadas por esta biblioteca, y el impacto se multiplica varias veces por cada dispositivo y app que usan.
Me gusta C, pero creo que una biblioteca usada por cientos de millones de personas en todo el mundo no debería usar C. En bibliotecas centrales como esta, la proporción de riesgo es demasiado alta. Incluso un experto en C se va a equivocar algún día.
Parece que esta es la corrección: https://github.com/webmproject/libwebp/commit/dce8397fec159c...
“malloc fail”, qué frustrante. Slack, Discord, Teams e incluso todos los sistemas operativos modernos están afectados.
C/C++ y los lenguajes dinámicos amplían mucho la superficie de comportamiento indefinido y bugs sutiles, y son difíciles y pesados de lintar incluso para los desarrolladores más brillantes. Las bibliotecas base deberían verificarse formalmente de una forma similar a seL4.
Otro problema es la falta de profesionalismo extendida en todo el FOSS, y el desprecio por la rigurosidad, la calidad, la exactitud y la seguridad. La forma actual de construir imperios sobre arena es insensata.