
CVE-2022-37706

Hola chicos, esta vez voy a hablar sobre un reciente 0-day que encontré en uno de los
principales gestores de ventanas de Linux llamado Enlightenment (https://www.enlightenment.org/).
Este 0-day lleva a cualquier usuario a privilegios de root muy fácil e instantáneamente.
El exploit está probado en Ubuntu 22.04, pero debería funcionar bien en cualquier distro.
En primer lugar, Enlightenment es un gestor de ventanas, compositor y escritorio mínimo
para Linux (la plataforma principal), BSD y cualquier otro sistema UNIX compatible.
Instalé este gestor de ventanas para experimentar un poco con él. Me resultó interesante
porque contiene muchas herramientas y, para ser honesto, se ve bastante limpio.
Después de instalar el paquete usando apt install enlightenment, examiné los
archivos y directorios instalados en mi sistema: muchos módulos y muchos binarios
auxiliares, pero lo más interesante es:
➜ enlightenment cd /usr/lib/x86_64-linux-gnu/enlightenment/
➜ enlightenment find . -perm -4000
./utils/enlightenment_ckpasswd
./utils/enlightenment_system
./utils/enlightenment_sys
Instala algunos binarios SUID, entonces pensé si podría usar uno de ellos
para escalar a root; los binarios parecían todos seguros y bien codificados.
El binario del que hablaremos es enlightenment_sys.
Como con cualquier otro objetivo, elegimos una estrategia a aplicar después de hacer una pre-evaluación;
consulta mi blog aquí si aún no lo has hecho (https://pwn-maher.blogspot.com/2020/10/vulnerability-assessment.html)
Audité el código con un enfoque de arriba hacia abajo (Top-Down).
Y como este gestor de ventanas es de código abierto, el código fuente estará disponible
para todos esos binarios y módulos.
Así que lo primero que hice fue apt source enlightenment para obtener todo el código fuente,
y con un poco de excavación podemos llegar al código del binario objetivo.
Pero para depurar el binario lo cargué en Ghidra para analizarlo y tener direcciones
para establecer puntos de interrupción y todo eso.
No se encontraron símbolos al primer intento, pero no los necesitamos porque resultó
ser un binario relativamente pequeño.
Sorprendentemente, me resultó mucho más agradable mirar el pseudo-código descompilado de
Ghidra que mirar directamente el código fuente (evita macros y también evita esas comprobaciones
sobre el sistema operativo usado para compilar un bloque de código específico).
Así que comencemos el análisis.
1- Jugar con el binario.
Ejecutemos el comando file para ver algo de información sobre nuestro objetivo:
Screenshot
Ejecutar el binario no da ninguna salida:
Screenshot
Pasar el argumento --help dio esta salida:
Screenshot
Lo siento, lo usaré para obtener root.
A continuación, usemos strace y veamos si utiliza alguna syscall sospechosa como
execve u openat:
strace ./enlightenment_sys 2>&1 | grep open
Screenshot
Solo abre bibliotecas conocidas en lugares donde no tenemos permiso para manipular.
strace ./enlightenment_sys 2>&1 | grep exec
Screenshot
2- Ingeniería inversa del binario y luego explotarlo.
Creé un nuevo proyecto de Ghidra y cargué este binario específico.
Como no se encontraron símbolos, podemos localizar la función main usando entry.
El primer argumento de la función entry es la propia main.
La renombré a main para futuras referencias.
Desplazándome un poco hacia abajo ya puedo ver que se usa la función system().
Como pwner, paso días en retos para invocar esta función específica x)
Hice ingeniería inversa del binario buscando un bug de corrupción de memoria o problemas de heap,
pero en realidad era una inyección de comandos extraña.
El binario toma todas las precauciones de seguridad antes de ejecutar system, pero lamentablemente
siempre podemos inyectar nuestra entrada allí.
Screenshot
Ok, ahora recorramos el binario desde arriba hasta nuestra función system, intentando
inyectar nuestra entrada allí.
Primero, el binario solo comprueba si el primer argumento es --help o -h y muestra ese
mensaje que vimos antes.
Screenshot
Segundo, eleva sus privilegios a root.
Screenshot
Luego, desestablece casi todas las variables de entorno (precauciones de seguridad) para
no invocar otro binario no intencionado.
Screenshot
Entonces, si el primer argumento que ingresamos es "mount", entrará en esta rama, comprobará algunas
banderas dadas, y esas banderas se establecerán en la pila.
Luego comprueba si el siguiente parámetro después de mount es UUID=; no queremos entrar
aquí, así que proporcionamos "/dev/../tmp/;/tmp/exploit".
Screenshot
Así pasamos la comprobación en la línea 410, la comprobación strncmp.
Porque si no comienza con /dev/, el binario saldrá.
A continuación hay una llamada a stat64 sobre ese archivo que proporcionamos; nota que podemos
crear una carpeta llamada ";" y eso causará la inyección de comandos.
Hasta ahora, el exploit ya creó este archivo /dev/../tmp/;/tmp/exploit,
pero este no es el exploit que será llamado.
Screenshot
Screenshot
Ya estamos más cerca de system().
Ahora p (puntero) se actualiza al último argumento dado a nuestro binario SUID,
/tmp///net.
¿Por qué proporcionar /tmp///net si podemos pasar /tmp/net?
Saltaremos esta comprobación:
if (((next_next == (char *)0x0) || (next_next[1] == '\0')) || ((long)next_next - (long)p != 6))
Necesitábamos que /tmp/net existiera y que /tmp/// tuviera una longitud de 6.
Ahora el último stat64 comprobará la existencia de "/dev/net"
__snprintf_chk(cmd,0x1000,1,0x1000,"/dev%s",next_next);
Y lo encontrará, así que pasamos esa última comprobación.
Ahora comprobará la disponibilidad de algunos archivos, pero eso no es importante
en este punto, porque estamos listos y muy cerca de desencadenar la ejecución
arbitraria de comandos.
Ahora eina_strbuf_new() simplemente inicializará el comando que se pasará a
system; el problema aquí es que lo ingresamos como:
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), "/dev/../tmp/;/tmp/exploit" /tmp///net
Pero el binario llama a eina_strbuf_append_printf() varias veces y se convierte en
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), /dev/../tmp/;/tmp/exploit /tmp///net
Observa que las comillas dobles se eliminan, y podremos llamar a /tmp/exploit
como root.
Screenshot
El binario hizo todo lo posible por mitigar cualquier comportamiento no intencionado, pero como siempre,
cualquier cosa puede ser pwneada. No esperaba explotar esto usando un bug lógico
como este.
Quiero que el próximo CVE sea una corrupción de memoria que conduzca a LPE root.