
# 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
| 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 |
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