
Exploit de preuve de concept en C pour CVE-2021-4034 (PwnKit) permettant une élévation de privilèges locale via une écriture hors limites dans pkexec. Comprend des instructions de compilation et une explication de la vulnérabilité.
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#
| Commande | Utilisation |
|---|---|
sudo chmod 0755 pkexec | Correction de CVE 2021-4034 |
git clone https://github.com/luijait/PwnKit-Exploit
cd PwnKit-Exploit
make
./exploit
whoami
| Commande | Utilité |
|---|---|
make clean | Nettoyer la construction pour tester le code modifié |
Basé sur le blog de qualys.com
Le début de la fonction main() de pkexec traite les arguments de la ligne de commande (lignes 534-568) et cherche le programme à exécuter, si son chemin n'est pas absolu, dans les répertoires de la variable d'environnement PATH (lignes 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 }
Malheureusement, si le nombre d'arguments de la ligne de commande argc est 0 – ce qui signifie que la liste d'arguments argv que nous passons à execve() est vide, i.e. {NULL} – alors argv[0] est NULL. C'est le terminateur de la liste d'arguments. Par conséquent :
à la ligne 534, l'entier n est définitivement fixé à 1 ; à la ligne 610, le pointeur path est lu hors limites depuis argv[1] ; à la ligne 639, le pointeur s est écrit hors limites dans argv[1]. Mais qu'est-ce qui est exactement lu et écrit dans cet argv[1] hors limites ?
Pour répondre à cette question, nous devons faire une brève digression. Lorsque nous exécutons execve() sur un nouveau programme, le noyau copie nos chaînes d'arguments et d'environnement, ainsi que les pointeurs (argv et envp), à la fin de la pile du nouveau programme ; par exemple :
|---------+---------+-----+------------|---------+---------+-----+------------|
| 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
Clairement, comme les pointeurs argv et envp sont contigus en mémoire, si argc est 0, alors argv[1] hors limites est en réalité envp[0], le pointeur vers notre première variable d'environnement, "value". Par conséquent :
À la ligne 610, le chemin du programme à exécuter est lu hors limites depuis argv[1] (c'est-à-dire envp[0]), et pointe vers "value" ; À la ligne 632, ce chemin "value" est passé à g_find_program_in_path() (car "value" ne commence pas par une barre oblique, à la ligne 629) ; Ensuite, g_find_program_in_path() recherche un fichier exécutable nommé "value" dans les répertoires de notre variable d'environnement PATH ; Si un tel fichier exécutable est trouvé, son chemin complet est renvoyé à la fonction main() de pkexec (à la ligne 632) ; Enfin, à la ligne 639, ce chemin complet est écrit hors limites dans argv[1] (c'est-à-dire envp[0]), écrasant ainsi notre première variable d'environnement. Donc, plus précisément :
Si notre variable d'environnement PATH est "PATH=name", et si le répertoire "name" existe (dans le répertoire de travail courant) et contient un fichier exécutable nommé "value", alors un pointeur vers la chaîne "name/value" est écrit hors limites dans envp[0] ; OU
Si notre PATH est "PATH=name=.", et si le répertoire "name=." existe et contient un fichier exécutable nommé "value", alors un pointeur vers la chaîne "name=./value" est écrit hors limites dans envp[0]. En d'autres termes, cette écriture hors limites nous permet de réintroduire une variable d'environnement « non sécurisée » (par exemple, LD_PRELOAD) dans l'environnement de pkexec. Ces variables « non sécurisées » sont normalement supprimées (par ld.so) de l'environnement des programmes SUID avant l'appel de la fonction main(). Nous exploiterons cette primitive puissante dans la section suivante.
Note de dernière minute : polkit prend également en charge les systèmes d'exploitation non Linux tels que Solaris et *BSD, mais nous n'avons pas étudié leur exploitabilité. Cependant, nous notons qu'OpenBSD n'est pas exploitable, car son noyau refuse d'exécuter execve() sur un programme si argc est 0.