
PoC
CVE-2022-37706

Salut les gars, cette fois je vais parler d'un récent 0-day que j'ai trouvé dans l'un des
principaux gestionnaires de fenêtres de Linux appelé Enlightenment (https://www.enlightenment.org/).
Ce 0-day va faire passer n'importe quel utilisateur aux privilèges root très facilement et instantanément.
L'exploit est testé sur Ubuntu 22.04, mais devrait fonctionner sur n'importe quelle distribution.
Tout d'abord, Enlightenment est un gestionnaire de fenêtres, compositeur et bureau minimal
pour Linux (la plateforme principale), BSD et tout autre système UNIX compatible.
J'ai installé ce gestionnaire de fenêtres pour expérimenter un peu avec. C'était intéressant
pour moi car il contient beaucoup d'outils et il a l'air plutôt sympa pour être honnête.
Après avoir installé le paquet avec apt install enlightenment, j'ai examiné les
fichiers et répertoires installés sur mon système, beaucoup de modules et beaucoup de binaires
d'aide, mais ce qui est le plus intéressant est :
➜ enlightenment cd /usr/lib/x86_64-linux-gnu/enlightenment/
➜ enlightenment find . -perm -4000
./utils/enlightenment_ckpasswd
./utils/enlightenment_system
./utils/enlightenment_sys
Il installe des binaires SUID, puis je me suis demandé si je pouvais utiliser l'un d'eux
pour escalader vers root, les binaires avaient tous l'air sécurisés et bien codés.
Le binaire dont nous allons parler est enlightenment_sys.
Comme pour toute autre cible, nous choisissons une stratégie à appliquer après avoir effectué une pré-évaluation,
voir mon blog ici si ce n'est pas encore fait (https://pwn-maher.blogspot.com/2020/10/vulnerability-assessment.html)
J'ai audité le code avec une approche descendante.
Et comme ce gestionnaire de fenêtres est open source, le code source sera disponible
pour tous ces binaires et modules.
Donc la première chose que j'ai faite a été apt source enlightenment pour obtenir tout le code source,
et avec un peu de recherche nous pouvons accéder au code du binaire cible.
Mais pour déboguer le binaire, je l'ai chargé dans Ghidra pour analyse et pour avoir des adresses
pour placer des points d'arrêt et tout.
Aucun symbole n'a été trouvé au premier essai mais bon, pas besoin de ceux-ci car il s'est avéré être
un binaire relativement petit.
Étonnamment, j'ai trouvé très agréable de regarder le pseudo-code décompilé de
Ghidra plutôt que de regarder directement le source (éviter les macros, éviter aussi ces vérifications
sur le système d'exploitation utilisé pour compiler un bloc de code spécifique).
Alors commençons l'analyse.
1- Jouer avec le binaire.
Lançons le fichier pour voir quelques informations sur notre cible :
Capture d'écran
L'exécution du binaire ne donne aucune sortie :
Capture d'écran
Donner l'argument --help a donné cette sortie :
Capture d'écran
Désolé, je vais l'utiliser pour obtenir root.
Ensuite, faisons un strace pour voir s'il utilise des appels système suspects comme
execve ou openat :
strace ./enlightenment_sys 2>&1 | grep open
Capture d'écran
Il ouvre juste des bibliothèques connues à des endroits où nous n'avons pas la permission de modifier.
strace ./enlightenment_sys 2>&1 | grep exec
Capture d'écran
2- Faisons du rétro-ingénierie sur le binaire puis exploitons-le.
J'ai créé un nouveau projet Ghidra, et j'ai chargé ce binaire spécifique.
Comme les symboles n'ont pas été trouvés, nous pouvons repérer la fonction main en utilisant entry.
Le premier argument de la fonction entry est main lui-même.
Je l'ai renommé main pour les références futures.
En défilant un peu vers le bas, je peux déjà repérer la fonction system() utilisée.
En tant que pwner, je passe des jours sur des défis pour faire apparaître cette fonction spécifique x)
J'ai rétro-conçu le binaire à la recherche d'un bug de corruption mémoire ou de problèmes de heap,
mais en réalité c'était une étrange injection de commande.
Le binaire prend toutes les précautions de sécurité avant d'exécuter system, mais malheureusement
nous pouvons toujours injecter notre entrée dedans.
Capture d'écran
Ok, maintenant parcourons le binaire du haut jusqu'à notre fonction system, en essayant
d'injecter notre entrée dedans.
D'abord, le binaire vérifie simplement si le premier argument est --help ou -h et affiche ce
message que nous avons vu plus tôt.
Capture d'écran
Deuxièmement, il élève ses privilèges à root.
Capture d'écran
Ensuite, il désactive presque toutes les variables d'environnement (précautions de sécurité) pour ne pas
invoquer un autre binaire non prévu.
Capture d'écran
Donc si le premier argument que nous avons saisi est "mount", il entrera dans cette branche, vérifiera certains
drapeaux donnés, ces drapeaux seront placés sur la pile.
Ensuite, il vérifie si le paramètre suivant après mount est UUID= ; nous ne voulons pas entrer
ici, donc nous avons donné "/dev/../tmp/;/tmp/exploit".
Capture d'écran
De cette façon, nous passons la vérification à la ligne 410, la vérification strncmp.
Parce que si cela ne commence pas par /dev/, le binaire se termine.
Ensuite, il y a un appel à stat64 sur ce fichier que nous avons fourni, notez que nous pouvons
créer un dossier appelé ";" et cela provoquera l'injection de commande.
Jusqu'à présent, l'exploit a déjà créé ce fichier /dev/../tmp/;/tmp/exploit,
mais ce n'est pas l'exploit qui sera appelé.
Capture d'écran
Capture d'écran
Nous nous rapprochons maintenant de system().
Maintenant, p (pointeur) est mis à jour avec le dernier argument donné à notre binaire SUID,
/tmp///net.
Pourquoi fournir /tmp///net alors que nous pouvons passer /tmp/net ?
Nous allons contourner cette vérification :
if (((next_next == (char *)0x0) || (next_next[1] == '\0')) || ((long)next_next - (long)p != 6))
Nous avions besoin que /tmp/net existe et que /tmp/// ait une longueur de 6.
Maintenant, le dernier stat64 vérifiera l'existence de "/dev/net"
__snprintf_chk(cmd,0x1000,1,0x1000,"/dev%s",next_next);
Et il le trouvera, donc nous passons cette dernière vérification.
Maintenant, il vérifiera la disponibilité de certains fichiers, mais ce n'est pas important
à ce stade, car tout est prêt et nous sommes proches de déclencher une exécution de commande
arbitraire.
Maintenant, eina_strbuf_new() initialise simplement la commande qui sera passée à
system, le problème ici est que nous l'avons saisie comme :
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), "/dev/../tmp/;/tmp/exploit" /tmp///net
Mais le binaire appelle eina_strbuf_append_printf() plusieurs fois et devient
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), /dev/../tmp/;/tmp/exploit /tmp///net
Remarquez que les guillemets doubles sont supprimés, et nous pourrons appeler /tmp/exploit
en tant que root.
Capture d'écran
Le binaire a fait de son mieux pour atténuer tout comportement non prévu mais comme d'habitude
tout peut être pwné. Je ne m'attendais pas à exploiter cela avec un bug logique
comme celui-ci.
Je veux que le prochain CVE soit une corruption mémoire menant à une LPE root.