
# Kit de détection et d'exploitation pour CVE-2026-39987, une RCE sans authentification dans les notebooks Marimo Comprend un scanner Python et un script Nmap NSE pour identifier les instances vulnérables via des vérifications sur le point de terminaison WebSocket.
Une vulnérabilité d'exécution de code à distance pré-authentifiée dans Marimo, un notebook Python open-source destiné à la science des données et à l'IA/ML. Le point de terminaison WebSocket du terminal (/terminal/ws) ignore complètement la validation d'authentification, tandis que le point de terminaison voisin du notebook (/ws) l'applique correctement. Un attaquant non authentifié peut se connecter à /terminal/ws et obtenir un shell PTY interactif complet sur le système hôte sans aucune information d'identification.
Exploitée dans la nature moins de 10 heures après la divulgation. Les attaquants ont volé des identifiants AWS en moins de 3 minutes.
Concerne Marimo <= 0.20.4. Corrigé dans Marimo 0.23.0.
| Champ | Détail |
|---|---|
| ID CVE | CVE-2026-39987 |
| Fournisseur | Projet Marimo |
| Produit | Marimo (Notebook Python) |
| Versions concernées | <= 0.20.4 |
| CVSS v3.1 | 9.3 (Critique) |
| CWE | CWE-306 — Authentification manquante pour une fonction critique |
| Vecteur d'attaque | Réseau |
| Authentification | Aucune requise |
| Interaction utilisateur | Aucune |
| Maturité de l'exploit | Activement exploitée dans la nature |
| Délai d'exploitation | ~10 heures après la divulgation |
| Corrigé dans | Marimo 0.23.0 |
Marimo est un notebook Python réactif open-source conçu comme une alternative moderne à Jupyter. Il est destiné à la science des données, à l'expérimentation IA/ML et à l'analyse interactive de données. Ses principales fonctionnalités incluent le suivi automatique des dépendances, l'exécution reproductible et une expérience de développement plus propre par rapport aux notebooks traditionnels.
Marimo gagne rapidement en popularité dans la communauté Python et IA/ML, en particulier auprès des praticiens qui souhaitent des flux de travail notebook plus structurés que ceux proposés par Jupyter.
Comme tous les environnements notebook, les instances Marimo ont généralement accès à des ressources sensibles : identifiants cloud (AWS, GCP, Azure), chaînes de connexion à des bases de données, clés API pour les services d'IA (OpenAI, Anthropic, etc.) et accès au réseau interne. Contrairement aux applications web traditionnelles, les notebooks sont conçus pour exécuter du code arbitraire. C'est leur objectif principal.
Cette combinaison rend tout contournement d'authentification dans un environnement notebook particulièrement dévastateur.``` Typical Marimo Deployment:
┌──────────────┐ ┌────────────────────────────────┐ │ │ HTTP │ Marimo Server │ │ Browser │────────>│ │ │ (User) │ │ ┌──────────────────────────┐ │ │ │<────────│ │ /ws (Notebook) │ │ └──────────────┘ WS │ │ ✅ validate_auth() │ │ │ └──────────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ /terminal/ws │ │ │ │ ❌ NO AUTH CHECK │ │ │ └──────────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │ │ Python Environment │ │ │ │ .env files │ │ │ │ AWS credentials │ │ │ │ API keys │ │ │ └──────────────────────────┘ │ └────────────────────────────────┘
---
## Analyse approfondie des vulnérabilités
### Les deux points de terminaison WebSocket
Le serveur de Marimo implémente plusieurs points de terminaison WebSocket pour différentes fonctionnalités. La différence critique entre les deux principaux points de terminaison réside dans la présence (ou l'absence) d'un contrôle d'authentification :```
Authentication Flow Comparison:
/ws (Notebook WebSocket):
┌─────────┐ ┌───────────────┐ ┌──────────┐ ┌───────────┐
│ Connect │───>│ validate_auth │───>│ Accept │───>│ Notebook │
└─────────┘ └───────┬───────┘ └──────────┘ └───────────┘
│
❌ Reject if
not authenticated
/terminal/ws (Terminal WebSocket):
┌─────────┐ ┌───────────────┐ ┌──────────┐ ┌───────────┐
│ Connect │───>│ Check mode & │───>│ Accept │───>│ PTY Shell │
└─────────┘ │ platform only │ └──────────┘ └───────────┘
└───────────────┘
⚠️ No auth check!
Anyone gets a shell!
Le point de terminaison du notebook (/ws) appelle correctement validate_auth() pour vérifier l'identité de l'utilisateur avant d'accepter les connexions WebSocket. C'est le comportement de sécurité attendu.
Le point de terminaison du terminal (/terminal/ws) vérifie uniquement si le serveur est en mode d'exécution et si la plateforme prend en charge les fonctionnalités du terminal. Il n'appelle jamais validate_auth(). Après avoir réussi ces vérifications de base, il accepte la connexion et crée une session PTY (pseudo-terminal) complète.```python
async def websocket_connect(self, message): await self.validate_auth() # ✅ Checks authentication await self.accept() # ... notebook communication
async def websocket_connect(self, message): if not self.is_running_mode(): # Only checks mode await self.close() return if not self.is_platform_supported(): # Only checks platform await self.close() return await self.accept() # ❌ No auth! Anyone gets a shell # ... PTY shell creation
Ceci est **CWE-306 : Absence d'authentification pour une fonction critique**. Le point de terminaison le plus dangereux du serveur (celui qui fournit un shell interactif) ne dispose d'aucune authentification.
### L'attaque : des avis aux clés AWS en 3 minutes