
Erkennungs- und Exploitation-Toolkit für CVE-2026-39987, eine Pre-Auth-RCE in Marimo-Notebooks. Enthält einen Python-Scanner und ein Nmap-NSE-Skript zur Identifizierung verwundbarer Instanzen über WebSocket-Endpunktprüfungen.
Eine Remote-Code-Execution-Schwachstelle ohne Authentifizierung in Marimo, einem Open-Source-Python-Notebook für Data Science und KI/ML. Der Terminal-WebSocket-Endpunkt (/terminal/ws) überspringt die Authentifizierungsprüfung vollständig, während der benachbarte Notebook-Endpunkt (/ws) diese korrekt durchsetzt. Ein nicht authentifizierter Angreifer kann sich mit /terminal/ws verbinden und ohne jegliche Anmeldedaten eine vollständige interaktive PTY-Shell auf dem Hostsystem erhalten.
Innerhalb von 10 Stunden nach der Offenlegung wurde die Schwachstelle bereits in freier Wildbahn ausgenutzt. Angreifer stahlen AWS-Anmeldedaten in weniger als 3 Minuten.
Betrifft Marimo <= 0.20.4. Behoben in Marimo 0.23.0.
| Feld | Detail |
|---|---|
| CVE-ID | CVE-2026-39987 |
| Anbieter | Marimo Project |
| Produkt | Marimo (Python-Notebook) |
| Betroffene Versionen | <= 0.20.4 |
| CVSS v3.1 | 9.3 (Kritisch) |
| CWE | CWE-306 — Fehlende Authentifizierung für kritische Funktion |
| Angriffsvektor | Netzwerk |
| Authentifizierung | Keine erforderlich |
| Benutzerinteraktion | Keine |
| Reifegrad des Exploits | Aktiv in freier Wildbahn ausgenutzt |
| Zeit bis zur Ausnutzung | ~10 Stunden nach Offenlegung |
| Behoben in | Marimo 0.23.0 |
Marimo ist ein Open-Source-reaktives Python-Notebook, das als moderne Alternative zu Jupyter entwickelt wurde. Es ist für Data Science, KI/ML-Experimente und interaktive Datenanalyse konzipiert. Zu den wichtigsten Funktionen gehören automatische Abhängigkeitsverfolgung, reproduzierbare Ausführung und eine sauberere Entwicklererfahrung im Vergleich zu herkömmlichen Notebooks.
Marimo gewinnt in der Python- und KI/ML-Community schnell an Bedeutung, insbesondere bei Praktikern, die strukturiertere Notebook-Workflows wünschen, als Jupyter sie bietet.
Wie alle Notebook-Umgebungen haben Marimo-Instanzen typischerweise Zugriff auf sensible Ressourcen: Cloud-Anmeldedaten (AWS, GCP, Azure), Datenbank-Verbindungsstrings, API-Keys für KI-Dienste (OpenAI, Anthropic usw.) sowie internen Netzwerkzugriff. Im Gegensatz zu herkömmlichen Webanwendungen sind Notebooks darauf ausgelegt, beliebigen Code auszuführen. Das ist ihr Kernzweck.
Diese Kombination macht jede Authentifizierungsumgehung in einer Notebook-Umgebung besonders verheerend.``` 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 │ │ │ └──────────────────────────┘ │ └────────────────────────────────┘
---
## Schwachstellen-Tiefenanalyse
### Die beiden WebSocket-Endpunkte
Der Marimo-Server implementiert mehrere WebSocket-Endpunkte für verschiedene Funktionen. Der entscheidende Unterschied zwischen den beiden Hauptendpunkten ist das Vorhandensein (oder Fehlen) einer Authentifizierungsprüfung:```
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!
Der Notebook-Endpunkt (/ws) ruft korrekt validate_auth() auf, um die Identität des Benutzers zu überprüfen, bevor WebSocket-Verbindungen akzeptiert werden. Dies ist das erwartete Sicherheitsverhalten.
Der Terminal-Endpunkt (/terminal/ws) prüft lediglich, ob sich der Server im laufenden Modus befindet und ob die Plattform Terminalfunktionen unterstützt. Er ruft niemals validate_auth() auf. Nachdem diese grundlegenden Prüfungen bestanden sind, akzeptiert er die Verbindung und erstellt eine vollständige PTY-Sitzung (Pseudo-Terminal).```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
Dies ist **CWE-306: Fehlende Authentifizierung für kritische Funktionen**. Der gefährlichste Endpunkt auf dem Server (derjenige, der eine interaktive Shell bereitstellt) hat keinerlei Authentifizierung.
### Der Angriff: Von der Beratung zu AWS-Keys in 3 Minuten