
# Lokale Docker-Umgebung zur Reproduktion von CVE-2026-39987, einer Pre-Auth-RCE in marimos Terminal-WebSocket. Vergleicht verwundbare und gepatchte Versionen mit PoC-Skripten mit geringstmöglichem Schadenspotenzial für die sicherheitstechnische Ausbildung.
Lokales Docker-Lab nur für lokale Zwecke zur Reproduktion und zum Verständnis von CVE-2026-39987 in marimo. Dieses Projekt vergleicht einen verwundbaren marimo-Dienst mit einem gepatchten marimo-Dienst nebeneinander und demonstriert dann den Unterschied mit einem Proof-Skript mit minimalem Schaden.
CVE-2026-39987 ist eine kritische Remote-Codeausführungsschwachstelle vor der Authentifizierung in marimo, einem reaktiven Python-Notebook-Framework.
Das verwundbare Verhalten existiert im Terminal-WebSocket-Endpunkt:
/terminal/ws
In betroffenen Versionen kann dieser Endpunkt ohne gültige Authentifizierung erreicht werden und eine interaktive Terminalsitzung erstellen. Ein nicht authentifizierter Angreifer, der einen verwundbaren marimo-Edit-Server erreichen kann, kann Befehle mit den Rechten des marimo-Prozesses ausführen.
Dieses Lab reproduziert die Schwachstelle in einer kontrollierten lokalen Docker-Umgebung:
| Dienst | Version | URL | Erwartetes Verhalten |
|---|---|---|---|
vuln | marimo 0.20.4 | http://127.0.0.1:8081 | /terminal/ws akzeptiert nicht authentifizierte WebSocket-Verbindung |
patched | marimo 0.23.0 | http://127.0.0.1:8082 | /terminal/ws lehnt nicht authentifizierte WebSocket-Verbindung mit 403 Forbidden ab |
Das Ziel ist nicht, einen einsatzbereiten Exploit bereitzustellen. Das Ziel ist, den beobachtbaren Sicherheitsunterschied zwischen verwundbaren und gepatchten Versionen mit lokalen, reproduzierbaren Beweisen zu zeigen.
Die betroffene Komponente ist der Terminal-WebSocket-Endpunkt von marimo:
/terminal/ws
Dieser Endpunkt wird von der marimo-Edit-Umgebung verwendet, um Terminalfunktionen über eine browserverbundene WebSocket-Sitzung bereitzustellen.
Der verwundbare Endpunkt akzeptierte WebSocket-Verbindungen, ohne dieselben Authentifizierungsprüfungen durchzusetzen, die für geschützte marimo-Edit-Funktionen erwartet werden.
Der wichtige Unterschied ist:
verwundbares Verhalten:
nicht authentifizierter Client kann sich mit /terminal/ws verbinden
Terminalsitzung wird erstellt
Befehle können über den WebSocket gesendet werden
gepatchtes Verhalten:
nicht authentifizierter Client wird abgelehnt
WebSocket-Handshake schlägt mit 403 Forbidden fehl
Terminalsitzung wird nicht erstellt
Der Patch fügt dem Terminal-WebSocket-Flow eine Authentifizierungsvalidierung hinzu, bevor eine Terminalsitzung eingerichtet werden darf.
Wenn ein verwundbarer marimo-Edit-Server in einem erreichbaren Netzwerk exponiert ist, kann ein nicht authentifizierter Angreifer Befehle als der Benutzer ausführen, der den marimo-Prozess ausführt.
In diesem Lab wird der marimo-Prozess absichtlich als Benutzer mit niedrigen Rechten und ohne Root-Rechte ausgeführt:
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
Dies hält die Demonstration sicherer, während die Schwachstelle dennoch bewiesen wird.
Dieses Lab soll drei Dinge beweisen:
/terminal/ws.403 Forbidden ab.Das Lab vermeidet absichtlich destruktive Befehle, Persistenz, Reverse Shells, Credential-Dumping oder jedes internetzugängliche Ziel.
.
├── docker-compose.yml
├── vuln/
│ ├── Dockerfile
│ └── notebook.py
├── patched/
│ ├── Dockerfile
│ └── notebook.py
├── poc/
│ ├── poc.py
│ ├── rce_poc.py
│ └── requirements.txt
├── SAFETY.md
├── README.md
└── .gitignore
| Datei | Zweck |
|---|---|
docker-compose.yml | Definiert verwundbare und gepatchte marimo-Dienste |
vuln/Dockerfile | Erstellt den verwundbaren marimo-Dienst |
patched/Dockerfile | Erstellt den gepatchten marimo-Dienst |
vuln/notebook.py | Minimale marimo-Notebook-Datei, die vom verwundbaren Dienst verwendet wird |
patched/notebook.py | Minimale marimo-Notebook-Datei, die vom gepatchten Dienst verwendet wird |
poc/poc.py | Proof-Skript mit minimalem Schaden, das nur harmlose Befehle ausführt |
poc/rce_poc.py | Echtzeit-Lernclient zur Beobachtung des Terminalverhaltens im lokalen Lab |
poc/requirements.txt | Python-Abhängigkeiten für PoC-Skripte |
SAFETY.md | Sicherheitsregeln und Scope-Grenzen |
Host-Maschine
127.0.0.1:8081 ─────► vuln-Container
marimo 0.20.4
/terminal/ws akzeptiert nicht authentifizierten WebSocket
127.0.0.1:8082 ─────► patched-Container
marimo 0.23.0
/terminal/ws lehnt nicht authentifizierten WebSocket ab
Beide Dienste exponieren den internen Port 2718 von marimo, aber die Host-Ports sind unterschiedlich:
vuln -> 127.0.0.1:8081
patched -> 127.0.0.1:8082
Die Dienste sind nur an 127.0.0.1 gebunden. Sie sind nicht dafür vorgesehen, einem LAN oder dem Internet ausgesetzt zu werden.
Die Container sind, wo möglich, mit mehreren Schutzmaßnahmen konfiguriert:
- Ports nur an 127.0.0.1 binden
- als Nicht-Root-Benutzer ausführen
- Linux-Capabilities entfernen
- no-new-privileges aktivieren
- schreibgeschütztes Root-Dateisystem verwenden
- nur begrenzte tmpfs-Schreiborte bereitstellen
- Dienste in einem dedizierten Docker-Bridge-Netzwerk isolieren
Diese Kontrollen entfernen die Schwachstelle nicht vom verwundbaren Dienst. Sie reduzieren den Schadensradius der lokalen Demonstration.
Getestete Annahmen:
- Linux x86_64 oder macOS mit Docker Desktop
- Docker Compose v2
- Python 3.9+
- Nur Localhost-Tests
Erforderliche Tools:
docker --version
docker compose version
python3 --version
In das Projektverzeichnis wechseln oder es öffnen:
cd cve-2026-39987
Beide Dienste bauen und starten:
docker compose up -d --build
Prüfen, dass beide Container laufen:
docker compose ps
Erwartete Dienste:
cve-2026-39987-vuln
cve-2026-39987-patched
Version des verwundbaren Dienstes prüfen:
docker compose exec vuln marimo --version
Erwartet:
0.20.4
Version des gepatchten Dienstes prüfen:
docker compose exec patched marimo --version
Erwartet:
0.23.0
Eine Python-Virtual-Environment erstellen:
python3 -m venv .venv
source .venv/bin/activate
Abhängigkeiten installieren:
python -m pip install -r poc/requirements.txt
Das primäre Proof-Skript ist:
poc/poc.py
Es versucht, sich mit folgendem zu verbinden:
/terminal/ws
Dann sendet es nur harmlose Proof-Befehle:
id
whoami
hostname