
Analyse technique et développement d'exploit pour CVE-2021-3156, un débordement de tampon basé sur le tas dans Sudo, y compris trois exploits fonctionnels pour l'élévation de privilèges locale sur les principales distributions Linux.
Qualys Security Advisory
Baron Samedit : Débordement de tampon basé sur le tas dans Sudo (CVE-2021-3156)
Résumé Analyse Exploitation Remerciements Chronologie
Nous avons découvert un débordement de tampon basé sur le tas dans Sudo (https://www.sudo.ws/). Cette vulnérabilité :
est exploitable par tout utilisateur local (utilisateurs normaux et utilisateurs système, sudoers et non-sudoers), sans authentification (c'est-à-dire que l'attaquant n'a pas besoin de connaître le mot de passe de l'utilisateur) ;
a été introduite en juillet 2011 (commit 8255ed69), et affecte toutes les versions héritées de 1.8.2 à 1.8.31p2 et toutes les versions stables de 1.9.0 à 1.9.5p1, dans leur configuration par défaut.
Nous avons développé trois exploits différents pour cette vulnérabilité, et obtenu les privilèges root complets sur Ubuntu 20.04 (Sudo 1.8.31), Debian 10 (Sudo 1.8.27), et Fedora 33 (Sudo 1.9.2). D'autres systèmes d'exploitation et distributions sont probablement aussi exploitables.
Si Sudo est exécuté pour lancer une commande en mode "shell" (shell -c commande) :
soit via l'option -s, qui définit le drapeau MODE_SHELL de Sudo ;
soit via l'option -i, qui définit les drapeaux MODE_SHELL et MODE_LOGIN_SHELL de Sudo ;
alors, au début de main() de Sudo, parse_args() réécrit argv (lignes 609-617), en concaténant tous les arguments de la ligne de commande (lignes 587-595) et en échappant tous les métacaractères avec des backslashes (lignes 590-591) :
Plus tard, dans sudoers_policy_main(), set_cmnd() concatène les arguments de la ligne de commande dans un tampon basé sur le tas "user_args" (lignes 864-871) et déséchappe les métacaractères (lignes 866-867), "à des fins de correspondance et de journalisation sudoers" :
Malheureusement, si un argument de ligne de commande se termine par un seul caractère backslash, alors :
à la ligne 866, "from[0]" est le caractère backslash, et "from[1]" est le terminateur nul de l'argument (c'est-à-dire pas un espace) ;
à la ligne 867, "from" est incrémenté et pointe vers le terminateur nul ;
à la ligne 868, le terminateur nul est copié dans le tampon "user_args", et "from" est à nouveau incrémenté et pointe vers le premier caractère après le terminateur nul (c'est-à-dire hors des limites de l'argument) ;
la boucle "while" aux lignes 865-869 lit et copie des caractères hors limites dans le tampon "user_args".
En d'autres termes, set_cmnd() est vulnérable à un débordement de tampon basé sur le tas, car les caractères hors limites qui sont copiés dans le tampon "user_args" n'ont pas été inclus dans sa taille (calculée aux lignes 852-853).
En théorie, cependant, aucun argument de ligne de commande ne peut se terminer par un seul caractère backslash : si MODE_SHELL ou MODE_LOGIN_SHELL est défini (ligne 858, une condition nécessaire pour atteindre le code vulnérable), alors MODE_SHELL est défini (ligne 571) et parse_args() a déjà échappé tous les métacaractères, y compris les backslashes (c'est-à-dire qu'il a échappé chaque backslash unique avec un second backslash).
En pratique, cependant, le code vulnérable dans set_cmnd() et le code d'échappement dans parse_args() sont entourés de conditions légèrement différentes :
vs :
Notre question, alors, est : pouvons-nous définir MODE_SHELL et soit MODE_EDIT soit MODE_CHECK (pour atteindre le code vulnérable) mais pas le MODE_RUN par défaut (pour éviter le code d'échappement) ?
La réponse, semble-t-il, est non : si nous définissons MODE_EDIT (option -e, ligne 361) ou MODE_CHECK (option -l, lignes 423 et 519), alors parse_args() supprime MODE_SHELL des "valid_flags" (lignes 363 et 424) et se termine avec une erreur si nous spécifions un drapeau non valide tel que MODE_SHELL (lignes 532-533) :
Mais nous avons trouvé une faille : si nous exécutons Sudo en tant que "sudoedit" au lieu de "sudo", alors parse_args() définit automatiquement MODE_EDIT (ligne 270) mais ne réinitialise pas "valid_flags", et les "valid_flags" incluent MODE_SHELL par défaut (lignes 127 et 249) :