1 puntos por GN⁺ 2024-01-27 | 1 comentarios | Compartir por WhatsApp
  • El commit 0226b56 de rhboot/shim corrige CVE-2023-40547, provocado por confiar directamente en el valor de tamaño de los encabezados HTTP durante la recepción de archivos
  • Si un encabezado manipulado especifica un tamaño menor que los datos realmente recibidos, shim puede asignar un espacio más pequeño que el búfer necesario
  • El código anterior usaba el valor del encabezado para la asignación y los metadatos del protocolo para la copia, lo que podía derivar en una escritura fuera de límites
  • El parche agrega una verificación de *buf_size < rx_message.BodyLength en receive_http_response() de httpboot.c y, si falla, lo trata como EFI_BAD_BUFFER_SIZE e Invalid Content-Length
  • El alcance del cambio es de 7 líneas agregadas y 1 línea eliminada en un único archivo, httpboot.c; también se corrigió el error tipográfico Content-Lenght a Content-Length

Flujo de la vulnerabilidad

  • CVE-2023-40547 es un problema que ocurre cuando shim obtiene un archivo mediante HTTP o protocolos relacionados
  • En el proceso de asignar el búfer para almacenar los datos recibidos, se usa el valor de tamaño de los encabezados HTTP
  • Los encabezados HTTP pueden ser manipulados y pueden especificar un tamaño menor que los datos realmente recibidos
  • El flujo anterior usaba el valor del encabezado para asignar el búfer y tomaba como referencia los metadatos del protocolo al copiar datos desde el búfer rx
  • Debido a esta diferencia, podía copiarse una cantidad de datos mayor que el búfer asignado y, como resultado, podía ocurrir una escritura fuera de límites

Contenido del parche

  • El parche agrega una verificación defensiva a receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) en httpboot.c
  • Cuando *buf_size == 0, corrige el error tipográfico del mensaje de error existente y pasa a goto error
    • Failed to get Content-LenghtFailed to get Content-Length
  • La nueva verificación comprueba la condición *buf_size < rx_message.BodyLength
    • Si la condición es verdadera, establece efi_status = EFI_BAD_BUFFER_SIZE
    • Imprime el error Invalid Content-Length
    • Luego pasa a goto error

Alcance del cambio

  • El único archivo modificado es httpboot.c
  • El volumen del cambio es de 7 líneas agregadas y 1 línea eliminada
  • El punto central es la lógica que comprueba si rx_message.BodyLength, la longitud del cuerpo recibido, es mayor que *buf_size, el tamaño asignado

Registro relacionado

  • Este commit está marcado como un cambio que resuelve CVE-2023-40547
  • El mensaje del commit indica explícitamente que el problema se originó por confiar incorrectamente en encabezados HTTP
  • El reporte de la vulnerabilidad figura a nombre de Bill Demirkapi, de Microsoft Security Response Center

1 comentarios

 
GN⁺ 2024-01-27
Opiniones en Hacker News
  • shim es un bootloader EFI usado con frecuencia por distribuciones Linux que quieren habilitar Secure Boot.
    Desde el punto de vista de una distribución, en vez de hacer que el usuario registre claves manualmente, se prefiere que pueda activar Secure Boot fácilmente con la clave firmada por Microsoft que ya viene incluida por defecto.
    Pero como Microsoft normalmente no firma bootloaders GPL como GRUB, se creó shim, que puede firmarse con la clave de Microsoft; shim verifica la firma de lo que va a arrancar usando una clave separada llamada Machine Owner Key, o MOK.
    Al indicar a shim el binario EFI que debe arrancar, se le puede dar una URL HTTP; si en ese caso el servidor HTTP es malicioso, puede provocar una escritura fuera de límites.
    Sin embargo, como normalmente se usa para arrancar un bootloader local de segunda etapa como GRUB, en la mayoría de las instalaciones parece poco probable que sea un problema.
    Secure Boot fue diseñado desde el principio para poder revocar incluso binarios ya firmados mediante la lista DBX; si esta lista se coloca en UEFI, ese binario se rechaza aunque tenga una firma válida.
    Si las firmas de binarios shim antiguos con este bug se agregan a la lista, cada persona puede actualizar la lista en su propio equipo; también podría distribuirse mediante actualizaciones en cápsula como LVFS y, si administras directamente las claves y variables de Secure Boot, también puedes descargar y registrar la lista desde https://uefi.org/revocationlistfile.

    • Soy quien descubrió el bug del artículo original, y es un malentendido común pensar que este problema solo se puede explotar cuando se usa arranque por HTTP.
      Si fuera así, no habría recibido una clasificación Critical.
      Este bug puede explotarse cuando malware local con privilegios sobrescribe la partición EFI, cuando hay un ataque de intermediario en una red adyacente con arranque PXE activado, o mediante un ataque remoto de intermediario al usar arranque por HTTP.
      Si un atacante remoto sin privilegios está en posición de intermediario y el equipo víctima usa arranque por HTTP, puede explotarlo sin acceso directo.
      Un atacante remoto que haya obtenido privilegios y ejecución de código en el equipo víctima puede eludir Secure Boot si el firmware soporta HTTP, aunque la víctima no use arranque por HTTP.
      Por ejemplo, puede cambiar las variables de orden de arranque para apuntar a un servidor controlado por el atacante, o sobrescribir el bootloader de la partición EFI con imágenes legítimas de shim y GRUB2, y luego hacer que grub.cfg cargue en cadena un nuevo shim por HTTP.
      Esto se debe a que la sintaxis de dispositivos de GRUB2 permite especificar dispositivos soportados, incluido HTTP.
      Además, si un atacante adyacente sin privilegios está en posición de intermediario y el equipo víctima usa arranque PXE, puede explotarlo encadenando shim por PXE → GRUB2 por PXE → shim por HTTP.
    • Porque el equipo legal de Microsoft considera que, si Microsoft firma GRUB, un bootloader con licencia GPLv3, la GPLv3 podría dar derecho a exigir que se entreguen las claves de firma a los desarrolladores.
      Fuente: https://techcommunity.microsoft.com/t5/hardware-dev-center/u...
    • Me preguntaba por qué un bootloader hacía comunicación de red, pero lo entendí con la explicación de que un binario EFI puede especificarse mediante una URL HTTP.
    • Me pregunto cómo shim evita el problema que preocupa a Microsoft.
      Si las cláusulas anti-Tivoization de la GPLv3 pueden exigir que se entreguen las claves de firma de Secure Boot, parecería que también habría que entregar las claves de firma MOK si se solicitan.
      En ese caso, cualquiera podría obtener una clave para firmar código arbitrario que se arrancaría indirectamente mediante Secure Boot, y no sé si eso es significativamente distinto de que Microsoft simplemente emita claves de firma a proyectos GPLv3 como GRUB.
    • Si el objetivo es arrancar un bootloader local de segunda etapa, creo que Windows Boot Manager también podría cumplir el mismo rol.
      En equipos BIOS antiguos alguna vez configuré WBM para que cargara GRUB en cadena, pero en equipos UEFI todavía no lo he probado, así que no sé si hay algún punto donde se bloquee.
  • Podrías preguntarte: “¿por qué arrancar desde un servidor no confiable o comprometido?” o “si el servidor fue comprometido, ¿no bastaría con que enviara un binario malicioso, haciendo que esto no importe?”. En resumen, el binario que shim arranca finalmente debe estar firmado con MOK.
    Por lo tanto, ya sea que se arranque dentro de una red comprometida, por HTTP o desde un servidor comprometido, deberían mantenerse las mismas garantías de seguridad independientemente de si se usa HTTPS o no.
    Secure Boot no impide downgrades, así que el hecho de que un servidor comprometido pueda usarse para un ataque de downgrade es independiente de esta vulnerabilidad.
    La prevención de ataques de downgrade, en cualquier caso, debe implementarse por separado con un método más robusto.
    Aun así, no sé por qué shim tendría que soportar directamente el arranque por HTTP.
    Podría manejarse en un segundo binario EFI local firmado con MOK, pero supongo que se consideró una función relativamente simple de implementar.

  • No entiendo por qué este código maneja la longitud del cuerpo con dos criterios distintos
    Según el RFC, en HTTP/1.1 Content-Length es la información autorizada sobre la longitud del cuerpo de una solicitud/respuesta HTTP
    Los datos que estén en la línea más allá de esa longitud son, por definición, parte de otro mensaje
    Por el contrario, si Content-Length es mayor que rx_message.BodyLength, significa que todavía no se recibió el mensaje completo, así que habría que esperar más o devolver un error de tiempo de espera
    En cualquiera de los casos, si no hay garantía de que rx_message.BodyLength sea igual a Content-Length, es un valor incorrecto
    Si se quiere tratar de forma más permisiva, no hay razón para mirar el encabezado Content-Length; basta con usar rx_message.BodyLength como tamaño del búfer e interpretar todos los datos en la línea como el mensaje recibido
    El código actual es innecesariamente complejo, y así es como se cuelan bugs

    • Si se mira solo ese commit, es fácil malinterpretarlo
      Al ver el código alrededor https://github.com/rhboot/shim/blob/0226b56513b2b8bd5fd281bc..., dentro del bucle recibe fragmentos de datos y cada vez verifica que los datos nuevos no superen la capacidad del búfer definida por Content-Length
      Pero antes no hacía esa comprobación para la primera lectura fuera del bucle, y ese era el bug
      Sin embargo, no veo código que al final compruebe que el tamaño descargado sea igual a *buf_size, es decir, a Content-Length
      Si esa condición no se cumple, podría ser señal de que la conexión se cerró demasiado pronto
  • Esto claramente es un bug y me alegra que se haya corregido, pero me pregunto quién arrancaría su equipo desde un host no confiable
    Si un atacante ya controla el servicio HTTP lo suficiente como para enviar encabezados maliciosos, evitar este overflow es el menor de los problemas: el certificado también estaría comprometido y podría enviar malware dentro de un payload conforme a la especificación
    Es cierto que es un bug, pero no estoy seguro de que sea Critical

    • La gente que impulsa Secure Boot es de una clase parecida
      Creen seriamente en una estrategia de seguridad en la que todo lo que potencialmente pueda verse comprometido debe no estar firmado por Secure Boot
      Si existe aunque sea una sola cosa firmada con una vulnerabilidad, se puede usar para descifrar los discos cifrados con Secure Boot+TPM de todo el mundo
      Me cuesta entender por qué este enfoque se consideró un modelo de seguridad válido, y ya hay montones de vulnerabilidades de este tipo
      Además, ignoran por completo el elefante en la habitación: Windows
      Un ejemplo de esta mentalidad: https://lkml.org/lkml/2018/4/3/767
      A pesar de las preocupaciones de Linus, en muchas distribuciones, al arrancar con Secure Boot efectivamente se activa el modo de integridad
      Probablemente se deba a las políticas de Microsoft y a que las distribuciones se ven obligadas a seguir el procedimiento descrito en ese hilo para obtener la firma UEFI de Microsoft
      Como resultado, al activar Secure Boot normalmente se restringen funciones de la distribución; por ejemplo, se impide usar la hibernación
    • Una buena defensa solo consiste en apilar varias capas de defensa, como en la defensa en profundidad, y este bug abre un agujero en una de esas capas
    • También podría usarse para entrar en algunos dispositivos bloqueados
    • Según la excelente explicación en otro hilo https://news.ycombinator.com/item?id=39135275, el vector de ataque no se limita a HTTP, así que puede considerarse Critical
  • Cuando ocurre algo importante, hay que usar la S de HTTP
    El arranque de dispositivos entra en esa categoría, y los encabezados HTTPS siempre han estado cifrados
    Aun así, buen hallazgo del bug

    • Aquí HTTPS no es relevante
      Se pueden enviar encabezados incorrectos por cualquiera de las dos vías
    • No estoy seguro de que HTTPS sea posible para este uso
      Porque para el cifrado se necesita la hora y fecha correctas
      El RTC podría ser válido, pero no sé si maneja bien las zonas horarias y, en cualquier caso, la hora podría estar desajustada
    • Es un problema independiente de si hay una S o no
      Parece que no entendiste bien el problema
  • Content-length no es la longitud real del cuerpo, sino la longitud después de Content-encoding

    • “HTTP/1.1 es un protocolo maravillosamente simple si ignoras casi todo”
  • ¿Estas builds de shim incluyen httpboot?
    Según entiendo, shim solo sirve para ejecutar otros binarios EFI desde el disco, y no creo haber visto que la funcionalidad de arranque por red de shim se use realmente

  • Quizás no sepa bien del tema, pero yo pensaba que la mayoría de los clientes HTTP solo leen hasta el Content-Length especificado, y consideran un error si los bytes leídos son menos que Content-Length

    • El cliente HTTP lo proporciona UEFI como driver EFI
      Por lo que veo, la especificación UEFI no parece definir en detalle qué debe ocurrir cuando el encabezado Content-Length y la longitud del cuerpo de la respuesta no coinciden
      Así que es totalmente posible que algunas implementaciones simplemente hagan una solicitud con connection:close y no verifiquen Content-Length
      Esta vulnerabilidad fue reportada por MSRC y la descripción del CVE no menciona explotación real
      Podría hacerse pública más adelante, o podría ser un problema teórico
  • ¿Alguien puede explicar por qué es peligroso leer menos que la longitud real del cuerpo?
    Yo habría esperado que lo peligroso fuera lo contrario

    • Al obtener un archivo por HTTP o por un protocolo relacionado, shim intenta asignar un búfer para almacenar los datos recibidos
      Pero toma el tamaño de un encabezado HTTP manipulable, y el atacante puede especificar un tamaño menor que los datos recibidos
      En ese caso, el código usa el valor del encabezado para la asignación y, al copiar desde el búfer de recepción, usa el tamaño de los metadatos del protocolo, lo que provoca una escritura fuera de límites
    • Según la explicación, asigna el búfer con base en Content-Length, pero copia tanto como el tamaño del búfer realmente recibido, escribiendo fuera del rango asignado