
# 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
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
Es werden keine Reverse Shell, kein Dateischreiben, keine Persistenz, kein Credential-Zugriff und keine destruktiven Befehle verwendet.
Ausführen:
python poc/poc.py --base-url http://127.0.0.1:8081
Erwartetes Ergebnis:
[*] Target WebSocket: ws://127.0.0.1:8081/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[+] websocket connected without credentials
[result] VULNERABLE: unauthenticated command execution observed
[proof]
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
Wenn das Skript rohe Terminalausgabe ausgibt, aber diesen Block enthält, ist die Schwachstelle weiterhin bestätigt:
CVE39987_PROOF_START
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
CVE39987_PROOF_END
Das bedeutet, dass die WebSocket-Verbindung erfolgreich war und die Befehlsausgabe von der Terminalsitzung zurückkam.
Ausführen:
python poc/poc.py --base-url http://127.0.0.1:8082
Erwartetes Ergebnis:
[*] Target WebSocket: ws://127.0.0.1:8082/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[-] websocket connection rejected/failed: Handshake status 403 Forbidden
[result] not exploitable by this unauthenticated check
Dies zeigt, dass der gepatchte Dienst nicht authentifizierten Zugriff ablehnt, bevor eine Terminalsitzung erstellt wird.
Die folgende Datei ist nur für lokales Lernen gedacht:
poc/rce_poc.py
Sie demonstriert dasselbe Problem in einer eher Echtzeit-Weise, indem sie sich mit dem verwundbaren Terminal-WebSocket verbindet und Terminalinteraktion ermöglicht.
Verwenden Sie dies nur gegen das lokale Docker-Lab.
python poc/rce_poc.py --base-url http://127.0.0.1:8081
Erwartetes Verhalten:
[+] Connected successfully (Pre-Auth RCE)
✓ Interactive shell is ready to use!
Sichere Befehle zum Ausprobieren:
id
whoami
hostname
pwd
python -c 'import marimo; print(marimo.__version__)'
exit
python poc/rce_poc.py --base-url http://127.0.0.1:8082
Erwartetes Verhalten:
[-] Connection Failed: 403 Forbidden
[!] This is likely the PATCHED version.
The WebSocket endpoint is now protected.
Der Echtzeitclient existiert, um zu verstehen, wie sich der verwundbare WebSocket verhält. Er sollte nicht gegen ein System außerhalb dieses lokalen Labs verwendet werden.
Für die Portfolio-Veröffentlichung ist der empfohlene primäre Beweis weiterhin poc/poc.py, da er begrenzt, wiederholbar und mit minimalem Schaden ist.
Das Hauptergebnis des Labs sollte wie folgt zusammengefasst werden:
vuln / marimo 0.20.4:
nicht authentifizierter WebSocket-Handshake zu /terminal/ws gelingt
Ausgabe harmloser Befehle ist beobachtbar
patched / marimo 0.23.0:
nicht authentifizierter WebSocket-Handshake zu /terminal/ws schlägt fehl
Server gibt 403 Forbidden zurück
keine Terminalsitzung wird erstellt
Dies ist der Kernbeweis für die CVE-Reproduktion.
Wenn Sie das Verhalten manuell überprüfen möchten, verwenden Sie einen WebSocket-fähigen Client und verbinden Sie sich mit:
ws://127.0.0.1:8081/terminal/ws
ws://127.0.0.1:8082/terminal/ws
Erwartet:
8081 -> Verbindung akzeptiert
8082 -> 403 Forbidden
Die PoC-Skripte werden bevorzugt, da sie klarere Beweise liefern.
In einer realen Umgebung sind nützliche Indikatoren:
- WebSocket-Anfragen an /terminal/ws
- nicht authentifizierte Zugriffsversuche auf marimo-Edit-Server
- unerwartete Terminalsitzungen, die von marimo erzeugt werden
- Befehle, die vom marimo-Prozess gestartet werden
- Zugriff auf .env, SSH-Schlüssel, Cloud-Credentials oder Notebook-Geheimnisse
- ausgehender Netzwerkverkehr kurz nach /terminal/ws-Verbindungen
Beispielhafte lokale Artefakte zur Überprüfung:
docker compose logs vuln
docker compose logs patched
docker compose ps
docker compose exec vuln ps aux
Container stoppen und entfernen:
docker compose down
Auch Volumes entfernen:
docker compose down -v
Lokale Python-Virtual-Environment bei Bedarf entfernen:
rm -rf .venv
Dieses Repository dient nur der lokalen Sicherheitsforschung und Portfolio-Demonstration.
Erlaubt:
- Localhost-Tests
- Nur-Docker-Reproduktion
- harmlose Proof-Befehle wie id, whoami, hostname
- Vergleich von verwundbarem und gepatchtem Verhalten
- Dokumentation von Grundursache und Erkennungsideen
Nicht erlaubt:
- Tests gegen öffentliche marimo-Server
- Tests von Systemen, die Sie nicht besitzen oder verwalten
- Reverse Shells
- Persistenz
- Credential-Diebstahl
- destruktive Befehle
- laterale Bewegung
- Botnet- oder Malware-Verhalten
Dieses Projekt ist nur für autorisierte lokale Tests und defensive Sicherheitsschulung gedacht. Verwenden Sie diese Skripte nicht gegen Systeme, die Sie nicht besitzen oder für die Sie keine ausdrückliche Erlaubnis zum Testen haben.
GitHub Security Advisory — GHSA-2679-6mx9-h9xc:
https://github.com/advisories/GHSA-2679-6mx9-h9xc
NVD — CVE-2026-39987:
https://nvd.nist.gov/vuln/detail/CVE-2026-39987
marimo-Upstream-Repository:
https://github.com/marimo-team/marimo
Patch-Commit — Authentifizierungsvalidierung zum Terminal-WebSocket hinzufügen:
https://github.com/marimo-team/marimo/commit/c24d4806398f30be6b12acd6c60d1d7c68cfd12a
Patch-PR — marimo PR #9098:
https://github.com/marimo-team/marimo/pull/9098
| 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 |