
PoC éducatif et analyse de la vulnérabilité d'escalade de privilèges locaux CVE-2021-4034 (PwnKit) dans pkexec de polkit, avec un laboratoire basé sur Docker pour une pratique concrète d'exploitation et de défense.
🔗 Projet original : berdav/CVE-2021-4034
Ce projet est une version analysée et modifiée à des fins éducatives basée sur l'original.
Sous licence MIT | Projet éducatif White Hat School
CVE-2021-4034 est une vulnérabilité d'élévation de privilèges locaux dans policykit-1 (PolicyKit) de Linux. Un utilisateur normal peut exécuter pkexec sans argument et exploiter une faille dans la structure mémoire du processus, ce qui amène glib à référencer à nouveau une chaîne de variable d'environnement qui aurait dû être filtrée, chargeant ainsi un fichier .so malveillant pour obtenir les privilèges root.
⚠️ Usage éducatif uniquement : ce code ne doit être utilisé que dans des systèmes modifiés. L'utiliser pour attaquer des systèmes réels peut entraîner des poursuites judiciaires.
| Élément | Détail |
|---|---|
| CVE ID | CVE-2021-4034 |
| Nom de la vulnérabilité | PwnKit |
| Versions affectées | Toutes les versions de polkit antérieures à 0.105 sans correctif (environnement de test : policykit-1 0.105-26ubuntu1 sur Ubuntu 20.04) |
| Type de vulnérabilité | Local Privilege Escalation (LPE) |
| Sévérité | Critique (CVSS 7.8) |
| Version corrigée | policykit-1 >= 0.105-26ubuntu1.1 |
| Date de découverte | Juin 2021 (divulguée en janvier 2022) |
Il est facile de penser qu'il s'agit d'un problème propre à Ubuntu, mais comme c'est un défaut de logique dans pkexec lui-même, la plupart des distributions utilisant polkit sont affectées. L'environnement de test Docker étant Ubuntu 20.04, j'ai noté cette version dans le tableau ci-dessus.
Au départ, j'ai pensé que c'était un problème dû à l'absence de validation des variables d'environnement, mais en examinant le code source et le commit de correction, j'ai réalisé que l'ordre était un peu différent. La véritable cause est ailleurs, et le problème des variables d'environnement est plutôt une conséquence de cette cause. Voici le flux réorganisé dans l'ordre des causes.
pkexec est un programme SUID-root qui demande une élévation de privilèges via PolicyKit.
# Exemple : exécuter une commande avec les privilèges root
pkexec /bin/id
pkexec systemctl restart service
Il est utilisé lorsqu'un utilisateur normal souhaite effectuer certaines tâches avec les droits d'administrateur.
argc == 0La fonction main() de pkexec, dans la partie traitant les arguments de la ligne de commande, ne vérifie pas le cas où aucun argument n'est fourni (argc == 0). C'est le véritable point de départ de cette vulnérabilité.
argv = {"pkexec", "commande", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0Lorsque argc est 0, la liste argv ne contient plus qu'un seul NULL indiquant la fin. Cependant, la logique interne de pkexec tente de lire et d'écrire dans argv[1] qui n'existe pas. Le problème est que Linux, lors de l'exécution d'un processus, place le tableau argv et le tableau envp (variables d'environnement) côte à côte en mémoire. Ainsi, l'accès à argv[1] hors limites pointe en fait vers envp[0], c'est-à-dire la première variable d'environnement.
Situation normale : argv = [ "pkexec" | NULL ]
Situation d'attaque : argv = [ NULL ] ← argc = 0
↑
Accès à argv[1] inexistant
↓
Lit et écrit dans envp[0] situé juste après en mémoire (hors limites)
Pourquoi est-ce dangereux :
GCONV_PATH, LD_PRELOAD avant l'exécution du programme SUID (pkexec) car elles sont considérées comme non sûres.argc < 1. (CWE-125 out-of-bounds read, CWE-787 out-of-bounds write)📌 En résumé : L'absence de validation des variables d'environnement est une 'condition permettant l'attaque', et la véritable cause racine est que pkexec ne gère pas le cas où argc == 0. Le point 3 ci-dessous est la conséquence de cette cause.
Grâce au comportement hors limites expliqué au point 2, lorsque pkexec initialise glib, cette chaîne est réutilisée sans être vérifiée.
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // 원래는 ld.so가 걸러냈어야 함
"CHARSET=PWNKIT", // 존재하지 않는 인코딩
};
execve("/usr/bin/pkexec", args, env); // argv는 비워서 argc=0을 만듦
Problème :
Ce qui est important ici, c'est que glib lui-même n'est pas en faute. Si GCONV_PATH est défini, chercher un convertisseur dans ce chemin est un comportement normal de glib. Le problème est que pkexec a déjà brisé l'état d'exécution sécurisée (l'état où les variables d'environnement dangereuses ont été supprimées) — glib n'a fait que fonctionner normalement, mais ce fonctionnement normal est exploité.
Vérification de la variable d'environnement CHARSET
CHARSET=PWNKIT
Recherche de la définition du convertisseur dans le fichier gconv-modules
module UTF-8// PWNKIT// pwnkit 1
Chargement du fichier .so depuis GCONV_PATH
GCONV_PATH=. → cherche pwnkit.so dans le répertoire courant
Exécution automatique de la fonction d'initialisation du .so
// pwnkit.c - .so 파일 로드 시 자동으로 실행됨
void gconv_init(void *step)
{
setuid(0); // root 권한 획득
setgid(0);
execve("/bin/sh"); // root shell 실행!
}
On pourrait facilement appeler gconv_init une 'fonction constructeur', mais strictement parlant, ce n'est pas comme __attribute__((constructor)) en C. C'est précisément une fonction d'initialisation définie dans l'interface du module gconv, que glib appelle explicitement après avoir chargé le .so avec dlopen.