- CVE-2024-1086 en
nf_tablesdel kernel de Linux genera una doble liberación desk_buffpor una falla de validación de entrada en los verdicts de Netfilter y, si se dan las condiciones, puede derivar en una escalada local de privilegios - El flujo de ataque hace que, durante el procesamiento de
NF_DROP, un skb ya liberado siga procesándose como si fueraNF_ACCEPT, de modo que el mismo objeto se libera nuevamente en una ruta posterior - El PoC fue validado en KernelCTF mitigation, Debian, Ubuntu y kernels vanilla; como mínimo, el rango v5.14.21~v6.6.14 queda dentro del impacto según la kconfig, y la corrección se distribuyó en las ramas stable en febrero de 2024
- La clave es Dirty Pagedirectory, una técnica KSMA solo de datos que asigna de forma duplicada páginas PTE y PMD a la misma página física para acceder a direcciones físicas arbitrarias solo con lecturas/escrituras desde espacio de usuario
- Al combinar búsqueda de KASLR físico, bypass de
modprobe_path, ejecución sin archivos y enlace de descriptores de archivo para escapar de namespaces, se convierte en un caso práctico de LPE que apunta a la vez a la gestión de memoria del kernel y al subsistema de red
Condiciones de la vulnerabilidad y alcance del impacto
- El bug de
nf_tablesfue registrado como CVE-2024-1086, y su punto central es una falla de validación de entrada en el manejo de verdicts de Netfilter del kernel de Linux, donde se permite un drop error positivo - El exploit requiere las siguientes condiciones
nf_tablesdebe estar habilitado- Los namespaces de usuario sin privilegios deben estar habilitados
- En distribuciones principales como Debian y Ubuntu, el hecho de que esta configuración venga habilitada por defecto es una premisa del ataque
- Según las pruebas, las ramas stable
linux-5.15.y,linux-6.1.yylinux-6.6.yestán dentro del impacto, ylinux-6.7.1también podría estarlo - En febrero de 2024 se distribuyó la corrección del bug en las ramas stable
- El código fuente del PoC está publicado en el repositorio del PoC de CVE-2024-1086
Flujo de código que produce la doble liberación
- Un verdict de Netfilter es un valor que decide si un paquete se descarta, se acepta, se envía a una cola, etc.
- El flujo vulnerable comienza porque
nft_verdict_init()no restringía lo suficiente el valor de verdict ingresado por el usuario, lo que permitía configurar un valor que parecíaNF_DROPpero cuyo drop error era positivo nf_hook_slow()mira los bits inferiores del verdict y, si lo interpreta comoNF_DROP, primero libera el skb conkfree_skb_reason()- Luego, si el resultado de
NF_DROP_GETERR()devuelve un valor correspondiente aNF_ACCEPT, el llamador considera que el paquete fue aceptado y continúa el procesamiento - Como resultado, un skb ya liberado se vuelve a liberar en una ruta posterior, creando un primitivo de double-free
Objeto corrompido y forma de procesamiento de paquetes
- La doble liberación afecta a
struct sk_buffenskbuff_head_cachey al objetosk_buff->head sk_buff->headcontiene el contenido real del paquete y, según el tamaño del paquete IPv4, puede asignarse desdekmalloc-256hasta páginas order 4 del buddy allocator- El PoC está diseñado para usar paquetes IP grandes y entrar por la ruta del buddy allocator, no por la del slab allocator
- La cola de fragmentación IPv4 se usa para retrasar la segunda liberación del skb o inducirla en el momento deseado
- Si en la ruta de paquetes se usan campos de un skb corrompido, el kernel puede entrar en pánico; por eso se evita la pila TCP/UDP y se usa una ruta específica de error de fragmentos IP
Alcance de pruebas y tasa de éxito
- Se probaron varias versiones y configuraciones de kernel en entornos de kernel vanilla, KernelCTF, Debian y Ubuntu
- Los casos exitosos incluyen los siguientes entornos
- Linux v5.14.21, v5.15.148, v5.16.20, v5.17.15, v5.18.19, v5.19.17, v6.0.19
- Linux v6.1.55 de KernelCTF Mitigation v3
- Linux v6.1.69 en Debian Bookworm 6.1.0-17
- Linux v6.1.72 de KernelCTF LTS
- Ubuntu Jammy v6.2.0-37
- Linux v6.2.16, v6.3.13
- Los casos fallidos incluyen v5.4.270, v5.10.209, v6.4.16, Ubuntu Jammy v6.5.0-15, v6.5.13, v6.6.14, v6.7.1, entre otros
- Algunos fallos posteriores a v6.4.0 están relacionados con la detección
bad_page()provocada porCONFIG_INIT_ON_ALLOC_DEFAULT_ON=y - En un entorno v6.4.16, la tasa de éxito fue de 99.4% y, según el caso, bajó hasta 93.0%; el tamaño de muestra fue n=1000 en ambos casos
Forma de corrección
- La primera corrección propuesta podía introducir un breaking change en medio del stack de Netfilter
- La corrección del maintainer de Netfilter restringe con mayor rigor, a nivel de API, los verdicts que vienen desde la entrada del usuario
- El parche rechaza los parámetros de verdict DROP/QUEUE provenientes de entradas de userland
- Según la descripción del CVE,
nft_verdict_init()permitía valores positivos como drop error dentro del hook verdict, ynf_hook_slow()podía generar una doble liberación cuandoNF_DROPyNF_ACCEPTse superponían - El parche de corrección puede consultarse en [PATCH nf] netfilter: nf_tables: reject QUEUE/DROP verdict parameters.
Dirty Pagedirectory y acceso arbitrario a memoria física
- La técnica central del PoC es Dirty Pagedirectory, una variante de la técnica existente Dirty Pagetable
- La idea principal es asignar de forma duplicada una página PTE y una página PMD a la misma página física
- Si se escribe en una región de direcciones virtuales un valor que parece un valor PTE, otra región de direcciones virtuales lo interpreta como una entrada de tabla de páginas y mapea la página física indicada
- Este método funciona como un kernel-space mirroring attack, que permite acceder a direcciones físicas arbitrarias configurando direcciones y flags de permisos únicamente con lecturas/escrituras de direcciones en espacio de usuario
- Se usa para evadir mitigaciones como virtual KASLR, KPTI, SMAP, SMEP y
CONFIG_STATIC_USERMODEHELPER
Manipulación del asignador de páginas
- En la asignación de páginas del kernel intervienen el slab allocator, el buddy allocator y el PCP allocator
- En tamaños grandes, el skb head usa páginas order 4 del buddy allocator, pero las páginas PTE/PMD son páginas order 0, por lo que no encajan directamente
- Para resolverlo se usan dos métodos de page conversion
- PCP list draining: colocar páginas order 4 en la freelist del buddy y vaciar la freelist order 0 de PCP para que el buddy allocator la rellene de nuevo con páginas order 0
- race condition: aprovechar una carrera durante la segunda liberación para colocar una página order 4 en la freelist order 0
- PCP list draining es el método más simple, estable y rápido
- El método basado en race condition se usó en el exploit inicial para KernelCTF, pero quedó obsoleto porque dependía de entornos con gran latencia en la TTY serial, como una VM QEMU
Bypass de KernelCTF mitigation
- En el entorno de KernelCTF mitigation, la mitigación que hubo que evadir activamente fue el chequeo de corrupción de la freelist de
sk_buff - Como
skbuff_head_cache->offset == 0x70, el puntero next de la freelist se superpone conskb->len - Tras la primera liberación del skb,
skb->lense sobrescribe con parte del puntero de la freelist y, durante el parseo posterior de paquetes, este valor puede cambiar y activar el chequeo de corrupción - La detección se evade liberando un skb normal adicional sobre el skb corrompido para sobrescribir la cabeza de la freelist
- Un desarrollador de KernelCTF consideró que esta evasión podría mitigarse si también se verifica, al momento de liberar, el puntero next de la cabeza de la freelist
TLB flush y búsqueda de KASLR físico
- Cuando Dirty Pagedirectory modifica las tablas de páginas de una forma inesperada, en la caché TLB de la CPU pueden quedar traducciones antiguas
- Desde espacio de usuario se hace un
fork()y luego el hijo ejecutamunmap()y queda durmiendo para hacer flush de la TLB - Se confirmó que este método funcionó al 100% en CPUs AMD y VMs QEMU
- La búsqueda de KASLR físico reduce el espacio de búsqueda aprovechando que la dirección base física del kernel está alineada con
CONFIG_PHYSICAL_STARToCONFIG_PHYSICAL_ALIGN - Suponiendo 8 GiB de memoria física y alineación de 16 MiB, hay 512 candidatos, que se identifican generando una firma de base del kernel con el script get-sig
modprobe_path y obtención de una root shell
- Después de obtener lectura/escritura arbitraria de memoria física, el PoC escanea un rango de unos 80 MiB después de la base del kernel para encontrar
modprobe_path - En configuraciones normales, busca el patrón
"/sbin/modprobe"con padding nulo y valida la variable real comprobando si se refleja en/proc/sys/kernel/modprobe - Si
CONFIG_STATIC_USERMODEHELPERestá habilitado, apunta a la cadena"/sbin/usermode-helper" - Para obtener una root shell, sobrescribe
modprobe_patho la cadena del static usermode helper con una ruta memfd del estilo/proc/<pid>/fd/<fd> - El script de privilege escalation conecta los descriptores de archivo del exploit con stdin/stdout de la shell, de modo que funcione tanto en una terminal local como en una reverse shell
Ejecución sin archivos y composición del PoC
- El PoC admite fileless execution, sin escribir archivos en disco
- Si el objetivo tiene Perl, se puede usar
memfd_create()para cargar el binario del exploit en memoria y ejecutarlo como/proc/$$/fd/<fd> - Las dependencias de compilación son
libnftnl-devylibmnl-dev - La build estática para KernelCTF usó
musl-gcc, una elección para evitar tanto el enlazado estático con glibc como el problema de opcodes AVX512 de QEMU - El código fuente del exploit está dividido en varios archivos y, al tener como objetivo un binario ejecutable independiente, ante errores opta por crash/exit en lugar de devolver error codes
Estabilidad y limitaciones
- Como el estado de la pagetable del proceso del exploit puede volverse inestable, después de un éxito o fallo se reduce la inestabilidad del kernel dejando al proceso hijo en estado sleep en lugar de finalizarlo
- Si hay actividad de red, se introduce ruido en la freelist de skb y eso puede afectar la estabilidad
- En entornos SSH o reverse shell, se reduce la salida por stdout cerca del momento del double-free para minimizar asignaciones/liberaciones de skb causadas por la red
- En algunas pruebas de hardware, el sistema crasheó unos segundos después; como los frames WiFi también usan skb, es posible que la actividad WiFi haya influido
- Al deshabilitar el adaptador WiFi desde la BIOS, el exploit funcionó correctamente en ese entorno
Conclusiones del proceso de investigación
- El PoC se refinó con el objetivo de lograr amplia compatibilidad, alta estabilidad y ejecución sigilosa
- Además de 2 meses de desarrollo, se dedicaron otros 2 meses a mejorar estabilidad y compatibilidad
- El exploit en sí no depende mucho del comportamiento del slab allocator, sino que se basa en funciones ampliamente habilitadas como el subsistema IPv4 y la memoria virtual
- El bug inicial requiere un user namespace sin privilegios y nftables, pero técnicas como Dirty Pagedirectory y PCP draining pueden aprovecharse también en otros exploits reales
- Este trabajo queda como un caso que profundiza simultáneamente en el subsistema de networking y el subsistema de memory management del kernel de Linux
1 comentarios
Comentarios en Hacker News
Hoy publicaron un exploit de prueba de concepto para CVE-2024-1086, y funciona en Debian y Ubuntu, entre otros
Las versiones afectadas son Linux kernel v5.14~v6.6, y la compatibilidad con v6.4~v6.6 depende de la configuración del kernel
CONFIG_INIT_ON_ALLOC_DEFAULT_ONEste bug fue parchado en febrero de 2024, así que hay que actualizar los equipos Linux
https://github.com/Notselwyn/CVE-2024-1086/blob/main/src/mai...
modprobe_pathe incluso añadieron fuerza bruta del pid por el enfoque filelessPor ejemplo, me gustaría preguntar por qué no eligieron algo como parchear el
.textdel kernel con shellcode corto para volverse root y escapar del namespaceAun así, indica que quedan excluidas las ramas parchadas
v5.15.149>,v6.1.76>,v6.6.15>En el parche aparece esta frase: “This reverts commit e0abdadcc6e1. [...] Its not clear to me why this commit was made.”
Me pregunto si alguien investigó el historial de contexto de ese commit
[1] https://lore.kernel.org/all/20240120215012.129529-1-fw@strle...
Busqué en la vieja lista de netdev, pero no encontré nada, y también podría haber sido un parche enviado directamente al committer Pablo Neira Ayuso
El autor original era Patrick McHardy, que ahora es una figura evitada, y a menos que Pablo lo recuerde o lo encuentre en sus correos, parece difícil aclarar el caso de uso exacto solo con una investigación básica
netfilter: nf_tables: accept QUEUE/DROP verdict parameters, que permite al espacio de usuario especificar un número de cola o un código errno para los veredictosQUEUEyDROPEste texto también es muy impresionante desde la perspectiva de la escritura de seguridad
Al escribir blogs de seguridad, siempre surge la duda de “cuánto conocimiento previo y de contexto asumir”, y lograr un equilibrio entre accesibilidad y profundidad es difícil
Como define bien a su público objetivo y ofrece suficiente contexto, me pareció excelente, así que lo guardé para dárselo como material guía a investigadores en formación que conozco cada año
Este exploit depende del acceso a user namespaces sin privilegios:
sysctl kernel.unprivileged_userns_clone = 1Es el valor por defecto en los kernels de Debian/Ubuntu y Arch Linux, y si no necesitas cosas como ejecutar comandos de Docker sin sudo, probablemente conviene desactivarlo
También está relacionada con el sandbox de Chrome que usan apps Electron o servicios como 1Password, y el binario auxiliar del sandbox también puede funcionar como programa setuid
Tampoco sorprendería que Proton empiece a usar user namespaces en el futuro, así que en Linux de escritorio general puede que sea mejor no desactivarlo
En servidores o en Linux endurecido para casos específicos, muchas veces no es mala idea desactivarlo
El problema es que, para que los contenedores “simplemente funcionen”, hacen falta muchísimos hacks del lado de red, y el principal punto de venta de Docker casi que consiste en encargarse de esos hacks peligrosos por ti
Al final, cualquier solución de contenedores termina aceptando esos hacks, y aunque la funcionalidad normal de namespaces reduce el acceso, el kernel vuelve a abrir la puerta por culpa de los hacks de red
No entiendo por qué los user namespaces sin privilegios vienen activados por defecto
Incluso si algo corre dentro de un namespace “sin privilegios”, me cuesta ver por qué por defecto se le da al usuario la capacidad de ejecutar cosas como iptables o mount
Por ejemplo, Chrome usa namespaces para implementar su sandbox de procesos, pero instala un binario setuid-root para no depender de namespaces sin privilegios
Como los binarios setuid-root son en sí mismos un riesgo de seguridad, a largo plazo sería preferible que Chrome no tuviera que instalar ese tipo de binarios
Pero para eso los user namespaces sin privilegios tendrían que estar ampliamente disponibles, y bugs como este terminan retrasando ese futuro
Además, programas como Chrome, que montan sandboxes basados en namespaces, suelen usar también seccomp para impedir que el código dentro del sandbox use funciones inusuales del kernel como los propios namespaces
En un escritorio de un solo usuario, la ventaja de imponer con fuerza la separación entre usuario y root no es tan grande, y la mayoría de las cosas interesantes ya son accesibles desde la cuenta del usuario
En cambio, los sandboxes tipo Chrome sí son esenciales para la seguridad del escritorio, así que en escritorios Linux de un solo usuario tiendo a pensar que habilitar user namespaces sin privilegios mejora la seguridad general
En sistemas multiusuario, claramente es otra historia
Si no siguieran apareciendo errores como este, los espacios de nombres de usuario sin privilegios habrían sido una excelente función de seguridad
Por ejemplo, sería bueno que ejecutar Flatpak no requiriera binarios
setuiddel host para aislar aplicacionesLa mayoría de los valores predeterminados no son seguros y requieren conocimiento técnico y trabajo de endurecimiento
Commit que introdujo el problema: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...
En un
switch/caseanidado, se ve extraño que haya unreturnen la ruta predeterminada y luego una estructura que espera un fall-through posteriorhttps://lwn.net/Articles/882397/
https://bugs.launchpad.net/bugs/cve/2024-1086
Una vulnerabilidad de use-after-free en el componente
netfilter: nf_tablesdel kernel de Linux permite elevación local de privilegiosLa función
nft_verdict_init()permite valores positivos como drop error dentro de una decisión de hook, y como resultadonf_hook_slow()puede procesarNF_DROPcon un drop error que pareceNF_ACCEPT, lo que puede generar una vulnerabilidad de doble liberaciónSe recomienda actualizar a una versión posterior a
f342de4e2f33e0e39165d8639387aa6c19dff660Me pregunto cómo siguen siendo posibles exploits como este aun con mitigaciones modernas como ASLR
En una clase de la universidad, nos dieron varios binarios para una versión específica de Ubuntu y la tarea era encontrar errores como use-after-free o buffer overflow y explotarlos, y fue realmente difícil
Encontrar el fallo ya era complicado, y escribir el shellcode exacto para hacer algo útil con ese fallo era todavía más difícil
En etapas más avanzadas, también estaban activadas mitigaciones como ASLR y stack canaries, y hasta en un entorno estudiantil controlado se sentía casi imposible
Al final, hay que 1) encontrar un fallo explotable y 2) hallar el payload binario exacto que haga algo útil sin simplemente hacer crashear el programa, y en el mundo real debe ser aún más difícil, así que me pregunto cómo lo logran
[0] https://web.stanford.edu/class/archive/cs/cs107/cs107.1194/a...
Mientras además llevaba otras materias, probablemente solo podía dedicar de unas horas a unos días por bug
Zerodium paga 50 mil dólares por una elevación local de privilegios típica en Linux, y tomando como referencia el costo de desarrolladores de exploits experimentados, eso compra más o menos 200 horas-persona
Es decir, los expertos están invirtiendo entre 10 y 100 veces más tiempo que un estudiante principiante
Basta pensar en la diferencia entre alguien que entra por primera vez a una carpintería y un carpintero, entre un principiante en cerámica y un artesano, o entre un pintor novato y un artista profesional
Y además con 10 a 100 veces más tiempo disponible
[1] https://zerodium.com/program.html
Existen técnicas “clásicas” para evadir la mayoría de las protecciones modernas y, si no las hay, aparecen nuevos ataques o nuevas formas de bypass
Por ejemplo, para bypass de protecciones del heap se puede ver how2heap[0], también hubo casos de exploits que evadieron KASLR[1], y este exploit parece usar la técnica de dirty pagetable[2]
Es una estructura continua de juego del gato y el ratón: se agregan mitigaciones y los investigadores encuentran cómo saltárselas
[0] https://github.com/shellphish/how2heap
[1] https://www.willsroot.io/2022/12/entrybleed.html
[2] https://pwning.tech/nftables/#452-the-technique
Para encontrar cosas así, basta con una persona del último tipo
Hoy en día es realmente difícil, y aun desactivando mitigaciones sigue sin ser fácil encontrar vulnerabilidades y escribir exploits
Aun así, muchas de las personas que buscan este tipo de fallos trabajan en equipo, paralelizan el fuzzing y pueden combinar conocimientos y otros exploits, o encadenarlos
La experiencia y el talento de algunos investigadores son sorprendentes, y en este campo tener años o décadas de experiencia vale muchísimo
Pero si pensamos que muchos de los bugs importantes que se descubren hoy los encuentran equipos bien financiados o actores estatales, resulta más fácil entender que puedan volcar enormes cantidades de personal y recursos para evadir mitigaciones existentes
Según Ubuntu, todas las versiones LTS están afectadas, y ya fue corregido en los kernels parcheados actuales: https://ubuntu.com/security/CVE-2024-1086
Focal fue corregido en
5.4.0-174.193, Jammy en5.15.0-101.111y Mantic en6.5.0-26.26Si usas soporte extendido, también incluye a Xenial y Bionic
Lo probé en un sistema Debian vulnerable y no logré la escalada de privilegios, pero en el segundo intento todo el sistema se quedó congelado
El primer intento simplemente falló, así que aun así vale totalmente la pena dedicar tiempo a aplicar el parche
La configuración actual del kernel se puede revisar en archivos como
/boot/configo/proc/config.gz