
分類: パープルチーム評価アーティファクト
許可された用途のみ。 本書および付属のテストスクリプトは、書面による許可が得られたシステム上での内部セキュリティ検証のみを目的としています。

| フィールド | 値 |
|---|---|
| CVE ID | CVE-2026-41651 |
| CWE | CWE-367 — Time-of-check Time-of-use (TOCTOU) 競合状態 |
| コンポーネント | PackageKit (packagekitd、D-Bus サービス) |
| 影響を受ける | PackageKit ≤ 1.3.4 |
| 修正バージョン | PackageKit 1.3.5 |
| 影響 | ローカル権限昇格 → root |
| 攻撃ベクトル | ローカル / D-Bus (非特権ユーザーセッション) |
| 公開日 | 2026-04-22 (Deutsche Telekom Red Team) |
| アドバイザリ | GHSA-f55j-vvr9-69xv |
| 修正コミット | 76cfb675fb31acc3ad5595d4380bfff56d2a8697 |
PackageKit は、システムパッケージ管理のための D-Bus 抽象化レイヤーであり、Debian、Ubuntu、Fedora、RHEL、SUSE、Arch Linux の GNOME ベースのデスクトップにデフォルトで存在します。非特権ユーザーに代わって特権パッケージ操作を仲介するため、その認可フローに存在する欠陥は、システム全体に root レベルの影響を及ぼします。
pk-transaction.c (1.3.5 より前) は、アクションメソッドの再呼び出しに対する状態ガードを強制していませんでした。D-Bus クライアントは、トランザクションオブジェクトがすでに PK_TRANSACTION_STATE_NEW から遷移した後でも、同じトランザクションオブジェクトに対して InstallFiles (または他のアクションメソッド) を複数回呼び出すことができました。
攻撃者 (非特権)
│
├─① CreateTransaction() → PackageKit がトランザクションオブジェクトパス (tid) を返す
│
├─② InstallFiles(tid, FLAG_SIMULATE=4, [dummy.pkg])
│ PackageKit は dummy.pkg の polkit 認可チェックをキューに入れる。
│ まだインストールは発生しない — SIMULATE はドライラン専用を意味する。
│
├─③ InstallFiles(tid, FLAG_NONE=0, [payload.pkg]) ← TOCTOU ウィンドウ
│ 認可が解決される前に同じ tid で再呼び出し。
│ 脆弱なバージョンはキューに入れられたパラメータを payload.pkg で上書きする。
│
└─④ polkit が認可を付与 (ユーザーが承認、または自動認可)
packagekitd が payload.pkg を root としてインストール。
payload の postinst/post スクリプト: install -m 4755 /bin/bash /tmp/.suid_bash
攻撃者は /tmp/.suid_bash -p を実行 → root シェル。
手順②と③は、同じ接続上でノンブロッキングの非同期 D-Bus 呼び出しとして送信され、単一の書き込みでフラッシュされます。この2つのメッセージは、packagekitd が②を処理して状態機械を進める前に到着するため、TOCTOU ウィンドウが開いたままになります。1.3.5 の修正では、PK_TRANSACTION_STATE_NEW 以降の再呼び出しに対して PK_TRANSACTION_ERROR_INVALID_STATE を返す明示的な状態チェックが追加されています。
ファイル: cve-2026-41651-purpleteam.py
言語: Python 3
依存関係: python3-gi (GObject introspection / GLib/Gio バインディング)
準備されたテストシステム上で CVE-2026-41651 の悪用可能性を実証します。目的は以下の通りです:
| フェーズ | アクション |
|---|---|
| セットアップ | /proc/mounts を調査して SUID/exec 可能なドロップディレクトリを選択し、umask(022) を設定、すべてのビルド成果物に明示的な chmod を適用し、/tmp にダミーとペイロードのパッケージをビルド |
| 悪用 | システム D-Bus 接続を開き、PackageKit トランザクションを作成し、2回呼び出しの競合を発火 |
| ペイロード | パッケージの post-install スクリプトが /bin/bash をモード 04755、所有者 root で <drop_dir>/.suid_bash にコピー。ドロップディレクトリは実行時に解決される (強化環境への対応 を参照) |
| 権限昇格 | SUID バイナリをポーリング (90 秒タイムアウト) し、-p 付きで execl して root シェルを取得 |
| クリーンアップ | 終了時 (成功または失敗) に一時的な .deb/.rpm ファイルを削除 |
デフォルトで強化されたシステムでは、2つの独立したポイントで悪用をブロックできます。スクリプトは両方を自動的に処理します。
027 / 077)プロセスが継承した umask が 027 または 077 の場合、mkdir() は 750 または 700 のディレクトリを生成します。dpkg-deb と rpmbuild の両方がビルドツリー全体を走査する必要があります — いずれかのディレクトリが読み取り不可の場合、ビルドは静かに失敗します。
適用される修正: os.umask(0o022) が、ファイルやディレクトリが作成される前に起動時に呼び出されます。さらに、ビルドツリー内のすべてのディレクトリとファイルは、作成直後に明示的な chmod を受け取ります (ディレクトリとスクリプトは 0o755、データファイルは 0o644)。これにより、継承された umask に関係なく正しいパーミッションが保証されます。
/tmp の nosuid / noexec マウントフラグ| フラグ | 悪用への影響 |
|---|---|
nosuid | カーネルはそのファイルシステムに保存されたファイルから SUID ビットを静かに削除する — コピーされた bash は root にならない |
noexec | SUID バイナリをまったく実行できない |
適用される修正: 起動時に、_find_suid_dir() が /proc/mounts を読み取り、どちらのフラグも設定されていないファイルシステムが見つかるまで、優先順位に従って候補ディレクトリをテストします。解決されたパスはペイロードの post-install スクリプトに組み込まれ、ポーリングと実行に使用されます。
| 優先度 | 候補 | 一般的な強化 |
|---|---|---|
| 1 | /var/tmp | nosuid/noexec が設定されることは稀; 再起動後も存続 |
| 2 | /dev/shm | tmpfs、通常は寛容; 再起動でクリア |
| 3 | /tmp | CIS/STIG システムではしばしば強化される |
| 4 | $HOME | 最後の手段; ユーザーが常に書き込み可能 |
すべての候補がブロックされている場合、スクリプトは静かに失敗するのではなく、明確なエラーで終了します。
| ディストリビューションファミリー | パッケージツール | テスト済み |
|---|---|---|
| Debian / Ubuntu | dpkg-deb | ✓ |
| RHEL / Fedora | rpmbuild | ✓ |
| SUSE / openSUSE | rpmbuild | ✓ |
IOC は、汎用/インフラストラクチャレベルからスクリプト固有のアーティファクトまでリストされています。汎用の指標に基づく検出は、使用される PoC のバリアントに関係なく、この CVE を捕捉します。
| IOC | パス / パターン | 備考 |
|---|---|---|
| ワールドライタブルディレクトリ内の SUID バイナリ | <drop_dir>/.suid_bash — ドロップディレクトリはマウントフラグに応じて /var/tmp、/dev/shm、/tmp、または $HOME | スクリプトは nosuid/noexec のない最初のディレクトリを選択; 検出を4つの候補すべてをカバーするように拡張 |
/tmp 内の一時パッケージファイル | /tmp/*.deb、/tmp/*.rpm | 非特権ユーザーによってビルド; 正当なパッケージ操作は /var/cache またはダウンロードパスを使用 |
| dpkg ビルドツリー | /tmp/pkbuild_*、/tmp/build_* | dpkg-deb -b のステージングディレクトリ |
/tmp 内の rpmbuild ツリー | /tmp/rpmbuild_* | ~/rpmbuild の外部で呼び出された rpmbuild のステージングディレクトリ |
/tmp 内の postinst / %post スクリプト | /tmp/*/DEBIAN/postinst、/tmp/*/SPECS/*.spec | システムバイナリをコピーまたは chmod するスクリプトは高信頼度 |
| IOC | 詳細 |
|---|---|
root 以外の apt 以外のプロセスによって生成された dpkg-deb | 親はユーザーの Python/シェルプロセスであり、apt、dpkg、unattended-upgrades ではない |
非特権ユーザーによって生成された rpmbuild | パッケージ関連の親 (mock、koji、CI ランナー) がない場合、これは異常 |
/tmp パスから /bin/sh または /bin/bash を生成する packagekitd | packagekitd は postinst スクリプトを実行; /tmp 内のソースパスが異常 |
UID ≠ EUID の bash プロセス | SUID 実行; EUID=0、UID=<攻撃者> |
bash -p の呼び出し | -p フラグは SUID が設定されている場合に特権モードを有効にする — 正当な使用は稀 |
短命な .suid_bash プロセスの祖先 | 祖父母は packagekitd、親は攻撃者のシェル |
| IOC | 詳細 |
|---|---|
同じオブジェクトパスで org.freedesktop.PackageKit.Transaction.InstallFiles が2回呼び出される | 両方の呼び出しがミリ秒以内に到着; 正当なクライアントはトランザクションごとに1回呼び出す |
同じ tid で FLAG_SIMULATE (4) の直後に FLAG_NONE (0) | このシーケンスに正当なユースケースはない |
CreateTransaction → 急速な二重 InstallFiles パターン | D-Bus モニター (dbus-monitor --system) または監査 dbus ルールで検出可能 |
/tmp 下のファイルパスを持つ InstallFiles | 正当な GUI パッケージインストーラー (GNOME Software、Discover) は $HOME または /var/cache のパスを使用 |
有効化:
auditctl -a always,exit -F arch=b64 -S all -F path=/usr/bin/packagekitd
または、以下のルールを/etc/audit/rules.d/で使用: