
# Laboratorio Docker locale che riproduce CVE-2026-39987, una RCE pre-autenticazione nel WebSocket del terminale di marimo. Confronta le versioni vulnerabile e corretta con script PoC a minimo impatto per ricerca sulla sicurezza a scopo didattico.
Laboratorio Docker solo-locale per riprodurre e comprendere CVE-2026-39987 in marimo. Questo progetto confronta un servizio marimo vulnerabile con un servizio marimo patchato affiancati, quindi dimostra la differenza utilizzando uno script di prova a danno minimo.
CVE-2026-39987 è una vulnerabilità critica di esecuzione remota di codice pre-autenticazione in marimo, un framework Python reattivo per notebook.
Il comportamento vulnerabile risiede nell'endpoint WebSocket del terminale:
/terminal/ws
Nelle versioni interessate, questo endpoint può essere raggiunto senza autenticazione valida e può creare una sessione di terminale interattiva. Un attaccante non autenticato che riesce a raggiungere un server di modifica marimo vulnerabile può eseguire comandi con i privilegi del processo marimo.
Questo laboratorio riproduce la vulnerabilità in un ambiente Docker locale controllato:
| Servizio | Versione | URL | Comportamento atteso |
|---|---|---|---|
vuln | marimo 0.20.4 | http://127.0.0.1:8081 | /terminal/ws accetta connessioni WebSocket non autenticate |
patched | marimo 0.23.0 | http://127.0.0.1:8082 | /terminal/ws rifiuta connessioni WebSocket non autenticate con 403 Forbidden |
L'obiettivo non è fornire un exploit weaponizzato. L'obiettivo è mostrare la differenza di sicurezza osservabile tra versioni vulnerabili e patchate utilizzando prove riproducibili e solo locali.
Il componente interessato è l'endpoint WebSocket del terminale di marimo:
/terminal/ws
Questo endpoint viene utilizzato dall'ambiente di modifica di marimo per fornire funzionalità di terminale tramite una sessione WebSocket collegata al browser.
L'endpoint vulnerabile accettava connessioni WebSocket senza applicare gli stessi controlli di autenticazione previsti per le funzionalità di modifica protette di marimo.
La differenza importante è:
comportamento vulnerabile:
un client non autenticato può connettersi a /terminal/ws
viene creata una sessione di terminale
i comandi possono essere inviati tramite il WebSocket
comportamento patchato:
un client non autenticato viene rifiutato
l'handshake WebSocket fallisce con 403 Forbidden
la sessione di terminale non viene creata
La patch aggiunge la validazione dell'autenticazione al flusso WebSocket del terminale prima di consentire l'instaurazione di una sessione di terminale.
Se un server di modifica marimo vulnerabile è esposto a una rete raggiungibile, un attaccante non autenticato può eseguire comandi come l'utente che esegue il processo marimo.
In questo laboratorio, il processo marimo viene eseguito intenzionalmente come utente non-root a bassi privilegi:
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
Questo mantiene la dimostrazione più sicura pur dimostrando comunque la vulnerabilità.
Questo laboratorio è progettato per dimostrare tre cose:
/terminal/ws.403 Forbidden.Il laboratorio evita intenzionalmente comandi distruttivi, persistenza, reverse shell, estrazione di credenziali o qualsiasi target esposto a internet.
.
├── docker-compose.yml
├── vuln/
│ ├── Dockerfile
│ └── notebook.py
├── patched/
│ ├── Dockerfile
│ └── notebook.py
├── poc/
│ ├── poc.py
│ ├── rce_poc.py
│ └── requirements.txt
├── SAFETY.md
├── README.md
└── .gitignore
Macchina host
127.0.0.1:8081 ─────► contenitore vuln
marimo 0.20.4
/terminal/ws accetta WebSocket non autenticato
127.0.0.1:8082 ─────► contenitore patched
marimo 0.23.0
/terminal/ws rifiuta WebSocket non autenticato
Entrambi i servizi espongono la porta interna 2718 di marimo, ma le porte host sono diverse:
vuln -> 127.0.0.1:8081
patched -> 127.0.0.1:8082
I servizi sono vincolati solo a 127.0.0.1. Non sono destinati a essere esposti a una LAN o a internet.
I contenitori sono configurati con diverse protezioni dove possibile:
- vincolare le porte solo a 127.0.0.1
- eseguire come utente non-root
- rimuovere le capability Linux
- abilitare no-new-privileges
- utilizzare un filesystem root di sola lettura
- fornire solo posizioni di scrittura tmpfs limitate
- isolare i servizi all'interno di una rete bridge Docker dedicata
Questi controlli non rimuovono la vulnerabilità dal servizio vulnerabile. Riducono il raggio d'esplosione della dimostrazione locale.
Assunzioni testate:
- Linux x86_64 o macOS con Docker Desktop
- Docker Compose v2
- Python 3.9+
- Test solo su localhost
Strumenti richiesti:
docker --version
docker compose version
python3 --version
Entrare nella directory del progetto:
cd cve-2026-39987
Compilare e avviare entrambi i servizi:
docker compose up -d --build
Verificare che entrambi i contenitori siano in esecuzione:
docker compose ps
Servizi attesi:
cve-2026-39987-vuln
cve-2026-39987-patched
Controllare la versione del servizio vulnerabile:
docker compose exec vuln marimo --version
Atteso:
0.20.4
Controllare la versione del servizio patchato:
docker compose exec patched marimo --version
Atteso:
0.23.0
Creare un ambiente virtuale Python:
python3 -m venv .venv
source .venv/bin/activate
Installare le dipendenze:
python -m pip install -r poc/requirements.txt
Lo script di prova principale è:
poc/poc.py
Tenta di connettersi a:
/terminal/ws
Quindi invia solo comandi di prova benigni:
id
whoami
hostname
Non viene utilizzata alcuna reverse shell, scrittura di file, persistenza, accesso alle credenziali o comando distruttivo.
Eseguire:
python poc/poc.py --base-url http://127.0.0.1:8081
Risultato atteso:
[*] Target WebSocket: ws://127.0.0.1:8081/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[+] websocket connected without credentials
[result] VULNERABLE: unauthenticated command execution observed
[proof]
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
Se lo script stampa l'output del terminale grezzo ma include questo blocco, la vulnerabilità è comunque confermata:
CVE39987_PROOF_START
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
CVE39987_PROOF_END
Ciò significa che la connessione WebSocket è riuscita e l'output del comando è tornato dalla sessione del terminale.
Eseguire:
python poc/poc.py --base-url http://127.0.0.1:8082
Risultato atteso:
[*] Target WebSocket: ws://127.0.0.1:8082/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[-] websocket connection rejected/failed: Handshake status 403 Forbidden
[result] not exploitable by this unauthenticated check
Questo mostra che il servizio patchato rifiuta l'accesso non autenticato prima di creare una sessione di terminale.
Il file seguente è destinato solo all'apprendimento locale:
poc/rce_poc.py
Dimostra lo stesso problema in modo più in tempo reale connettendosi al WebSocket del terminale vulnerabile e consentendo l'interazione con il terminale.
Utilizzarlo solo contro il laboratorio Docker locale.
python poc/rce_poc.py --base-url http://127.0.0.1:8081
Comportamento atteso:
[+] Connected successfully (Pre-Auth RCE)
✓ Interactive shell is ready to use!
Comandi sicuri da provare:
id
whoami
hostname
pwd
python -c 'import marimo; print(marimo.__version__)'
exit
python poc/rce_poc.py --base-url http://127.0.0.1:8082
Comportamento atteso:
[-] Connection Failed: 403 Forbidden
[!] This is likely the PATCHED version.
The WebSocket endpoint is now protected.
Il client in tempo reale esiste per aiutare a comprendere come si comporta il WebSocket vulnerabile. Non dovrebbe essere utilizzato contro alcun sistema al di fuori di questo laboratorio locale.
Per la pubblicazione in portfolio, la prova primaria consigliata rimane poc/poc.py, perché è limitata, ripetibile e a danno minimo.
Il risultato principale del laboratorio dovrebbe essere riassunto come:
vuln / marimo 0.20.4:
l'handshake WebSocket non autenticato a /terminal/ws riesce
l'output del comando benigno è osservabile
patched / marimo 0.23.0:
l'handshake WebSocket non autenticato a /terminal/ws fallisce
il server restituisce 403 Forbidden
nessuna sessione di terminale viene creata
Questa è la prova principale per la riproduzione della CVE.
Se si desidera ispezionare manualmente il comportamento, utilizzare un client compatibile con WebSocket e connettersi a:
ws://127.0.0.1:8081/terminal/ws
ws://127.0.0.1:8082/terminal/ws
Atteso:
8081 -> connessione accettata
8082 -> 403 Forbidden
Gli script PoC sono preferiti perché producono prove più chiare.
In un ambiente reale, gli indicatori utili includono:
- richieste WebSocket a /terminal/ws
- tentativi di accesso non autenticato ai server di modifica marimo
- sessioni di terminale inattese generate da marimo
- comandi avviati dal processo marimo
- accesso a .env, chiavi SSH, credenziali cloud o segreti del notebook
- traffico di rete in uscita subito dopo le connessioni a /terminal/ws
Esempi di artefatti locali da ispezionare:
docker compose logs vuln
docker compose logs patched
docker compose ps
docker compose exec vuln ps aux
Arrestare e rimuovere i contenitori:
docker compose down
Rimuovere anche i volumi:
docker compose down -v
Rimuovere l'ambiente virtuale Python locale se desiderato:
rm -rf .venv
Questo repository è destinato esclusivamente alla ricerca di sicurezza locale e alla dimostrazione per portfolio.
Consentito:
- test su localhost
- riproduzione solo Docker
- comandi di prova benigni come id, whoami, hostname
- confronto tra comportamento vulnerabile e patchato
- documentazione della causa principale e delle idee di rilevamento
Non consentito:
- test contro server marimo pubblici
- test di sistemi che non si possiedono o non si amministrano
- reverse shell
- persistenza
- furto di credenziali
- comandi distruttivi
- movimento laterale
- comportamento botnet o malware
Questo progetto è destinato esclusivamente a test locali autorizzati e all'educazione sulla sicurezza difensiva. Non utilizzare questi script contro sistemi che non si possiedono o per i quali non si ha esplicita autorizzazione al test.
GitHub Security Advisory — GHSA-2679-6mx9-h9xc:
https://github.com/advisories/GHSA-2679-6mx9-h9xc
NVD — CVE-2026-39987:
https://nvd.nist.gov/vuln/detail/CVE-2026-39987
Repository upstream di marimo:
https://github.com/marimo-team/marimo
Commit della patch — aggiunta della validazione dell'autenticazione al WebSocket del terminale:
https://github.com/marimo-team/marimo/commit/c24d4806398f30be6b12acd6c60d1d7c68cfd12a
PR della patch — marimo PR #9098:
https://github.com/marimo-team/marimo/pull/9098
| File | Scopo |
|---|
docker-compose.yml | Definisce i servizi marimo vulnerabile e patchato |
vuln/Dockerfile | Compila il servizio marimo vulnerabile |
patched/Dockerfile | Compila il servizio marimo patchato |
vuln/notebook.py | File notebook marimo minimale utilizzato dal servizio vulnerabile |
patched/notebook.py | File notebook marimo minimale utilizzato dal servizio patchato |
poc/poc.py | Script di prova a danno minimo che esegue solo comandi benigni |
poc/rce_poc.py | Client di apprendimento in tempo reale per osservare il comportamento del terminale nel laboratorio locale |
poc/requirements.txt | Dipendenze Python per gli script PoC |
SAFETY.md | Regole di sicurezza e limiti di ambito |