
Kit de detección y explotación para CVE-2026-39987, un RCE sin autenticación previa en notebooks Marimo. Incluye un escáner en Python y un script NSE de Nmap para identificar instancias vulnerables mediante comprobaciones del endpoint WebSocket.
Una vulnerabilidad de ejecución remota de código pre-autenticada en Marimo, un notebook Python de código abierto para ciencia de datos e IA/ML. El endpoint WebSocket de terminal (/terminal/ws) omite por completo la validación de autenticación, mientras que el endpoint vecino de notebook (/ws) la aplica correctamente. Un atacante no autenticado puede conectarse a /terminal/ws y obtener un en el sistema host sin credenciales.
Explotada en la naturaleza en menos de 10 horas desde su divulgación. Los atacantes robaron credenciales de AWS en menos de 3 minutos.
Afecta a Marimo <= 0.20.4. Corregido en Marimo 0.23.0.
| Campo | Detalle |
|---|---|
| ID CVE | CVE-2026-39987 |
| Proveedor | Proyecto Marimo |
| Producto | Marimo (Notebook Python) |
| Versiones Afectadas | <= 0.20.4 |
| CVSS v3.1 | 9.3 (Crítico) |
| CWE | CWE-306 — Autenticación Ausente para Función Crítica |
| Vector de Ataque | Red |
| Autenticación | No se requiere |
| Interacción del Usuario | Ninguna |
| Madurez del Exploit | Explotado activamente en la naturaleza |
| Tiempo hasta la Explotación | ~10 horas después de la divulgación |
| Corregido En | Marimo 0.23.0 |
Marimo es un notebook Python reactivo de código abierto diseñado como una alternativa moderna a Jupyter. Está construido para ciencia de datos, experimentación con IA/ML y análisis de datos interactivo. Sus características clave incluyen seguimiento automático de dependencias, ejecución reproducible y una experiencia de desarrollo más limpia en comparación con los notebooks tradicionales.
Marimo está ganando rápidamente tracción en la comunidad de Python e IA/ML, especialmente entre los profesionales que desean flujos de trabajo de notebook más estructurados de los que ofrece Jupyter.
Como todos los entornos de notebook, las instancias de Marimo suelen tener acceso a recursos sensibles: credenciales en la nube (AWS, GCP, Azure), cadenas de conexión a bases de datos, claves API para servicios de IA (OpenAI, Anthropic, etc.) y acceso a la red interna. A diferencia de las aplicaciones web tradicionales, los notebooks están diseñados para ejecutar código arbitrario. Ese es su propósito principal.
Esta combinación hace que cualquier omisión de autenticación en un entorno de notebook sea particularmente devastadora.``` 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 │ │ │ └──────────────────────────┘ │ └────────────────────────────────┘
---
## Análisis Profundo de la Vulnerabilidad
### Los Dos Endpoints WebSocket
El servidor de Marimo implementa múltiples endpoints WebSocket para diferentes funciones. La diferencia crítica entre los dos endpoints principales es la presencia (o ausencia) de una verificación de autenticación:```
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!
El endpoint del notebook (/ws) llama correctamente a validate_auth() para verificar la identidad del usuario antes de aceptar conexiones WebSocket. Este es el comportamiento de seguridad esperado.
El endpoint del terminal (/terminal/ws) solo verifica si el servidor está en modo de ejecución y si la plataforma soporta la funcionalidad del terminal. Nunca llama a validate_auth(). Después de pasar estas verificaciones básicas, acepta la conexión y crea una sesión PTY (pseudo-terminal) completa.```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
Esto es **CWE-306: Falta de Autenticación para Funciones Críticas**. El endpoint más peligroso del servidor (el que proporciona un shell interactivo) no tiene autenticación alguna.
### El Ataque: De Advisory a Claves de AWS en 3 Minutos
La línea de tiempo de explotación muestra lo rápido que operan los actores de amenazas modernos:```
┌──────────────────────────────────────────────────────────────┐
│ CVE-2026-39987 Timeline │
├──────────────────────────────────────────────────────────────┤
│ │
│ T+0h Advisory published │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ T+9h First exploit built from advisory │
│ T+10h Exploitation in the wild confirmed │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ T+10h 0m Attacker connects to /terminal/ws │
│ T+10h 1m Full PTY shell obtained │
│ T+10h 2m .env file located and read │
│ T+10h 3m AWS keys exfiltrated │
│ Total attack time: ~3 minutes │
└──────────────────────────────────────────────────────────────┘
El ataque en sí es trivialmente simple:``` Step 1: Attacker opens WebSocket connection to /terminal/ws (No authentication needed, no special tools required)
Step 2: Server creates a PTY (pseudo-terminal) session Attacker now has an interactive shell
Step 3: Attacker runs commands: $ cat .env AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... DATABASE_URL=postgres://... OPENAI_API_KEY=sk-...
Step 4: Credentials exfiltrated Attacker now has cloud access, database access, and API keys for AI services
Total time: under 3 minutes Authentication required: none Tools required: any WebSocket client
No se necesita desarrollo de exploits. Sin shellcode. Sin corrupción de memoria. Solo un cliente WebSocket y una verificación de autenticación ausente.
---
## Análisis de Impacto
**Impacto inmediato en el host de Marimo:**
- Shell interactivo completo con los privilegios del proceso de Marimo
- Acceso a todos los archivos legibles por el proceso (código fuente, datos, credenciales)
- Acceso a variables de entorno que contienen claves API y secretos
- Capacidad de ejecutar comandos arbitrarios en el sistema host
**Exposición de credenciales (el objetivo principal del ataque):**
- Claves de acceso y claves secretas de AWS desde archivos `.env` o variables de entorno
- Credenciales de cuentas de servicio de GCP/Azure
- Cadenas de conexión a bases de datos con contraseñas
- Claves API de OpenAI, Anthropic y otros servicios de IA
- Claves SSH y otro material de autenticación
**Impacto posterior (mediante credenciales robadas):**
- Acceso no autorizado a infraestructura en la nube (EC2, S3, Lambda, etc.)
- Exfiltración de datos desde almacenamiento en la nube y bases de datos
- Abuso de recursos (criptominería, robo de créditos de API de IA)
- Movimiento lateral hacia redes en la nube y locales
**Factores de amplificación de riesgo:**
- Los entornos de notebooks están diseñados para ejecutar código arbitrario (ese es su propósito)
- Los entornos de ciencia de datos suelen tener acceso amplio a la nube para trabajos de entrenamiento
- Muchas instancias de Marimo están expuestas a internet para colaboración y trabajo remoto
- El endurecimiento de seguridad suele ser una ocurrencia tardía en entornos de investigación/experimentación
---
## Versiones Afectadas
| Versión | Estado |
|---------|--------|
| Marimo 0.23.0+ | **Corregido** |
| Marimo 0.20.5 a 0.22.x | **Probablemente vulnerable** (entre el rango del aviso y la corrección) |
| Marimo <= 0.20.4 | **Vulnerable** (rango confirmado) |
---
## El Panorama General: La Cadena de Herramientas de IA/ML Bajo Ataque
CVE-2026-39987 no es un incidente aislado. Es parte de un patrón claro que surgió en abril de 2026:
| CVE | Producto | Tipo | Estado |
|:---|:---|:---|:---|
| **CVE-2026-39987** | Marimo | RCE Pre-Auth (WebSocket) | Explotado en 10 horas |
| **CVE-2026-33017** | Langflow | RCE | CISA KEV (26 de marzo) |
| **CVE-2026-5059** | aws-mcp-server | Inyección de comandos RCE | Aviso público |
| **TorchGeo** | TorchGeo | RCE por eval() | Aviso público |
Cuatro herramientas de desarrollo de IA/ML afectadas por vulnerabilidades críticas de RCE en un solo mes. El pipeline de desarrollo de IA/ML se está convirtiendo en el nuevo shadow IT: herramientas desplegadas con acceso amplio, supervisión de seguridad mínima y almacenes de credenciales ricos.```
┌─────────────────────────────────────────────────┐
│ Why AI/ML Tools Are Prime Targets │
├─────────────────────────────────────────────────┤
│ │
│ 1. DESIGNED to execute arbitrary code │
│ (that's literally what notebooks do) │
│ │
│ 2. Run with elevated privileges │
│ (GPU access, cloud SDKs, network access) │
│ │
│ 3. Contain high-value credentials │
│ (AWS keys, API tokens, DB connections) │
│ │
│ 4. Often exposed to the network │
│ (for collaboration and remote access) │
│ │
│ 5. Security hardening is an afterthought │
│ (focus on features and UX, not security) │
│ │
│ 6. Users are researchers, not security experts │
│ (default configs, weak passwords, no VPN) │
└─────────────────────────────────────────────────┘
El script en Python detecta instancias vulnerables de Marimo mediante un análisis de múltiples pasos.
Cómo funciona:
/api/status, /api/health y / en busca de indicadores de Marimo en el contenido de las respuestas y cabeceras/terminal/ws (no se transmiten datos a través de la conexión)/terminal/ws (debería requerir autenticación) frente a /ws (se sabe que requiere autenticación) para confirmar la inconsistenciaNo se ejecutan comandos en los sistemas objetivo. El handshake WebSocket se prueba, pero no se envían datos a través de la conexión. La comprobación es completamente pasiva y segura para producción.
Uso:```bash
pip install -r requirements.txt
python CVE-2026-39987_Marimo_RCE_detector.py -t http://marimo-host:2718
python CVE-2026-39987_Marimo_RCE_detector.py -t https://marimo-host:443
python CVE-2026-39987_Marimo_RCE_detector.py -f targets.txt -o results.json -v
python CVE-2026-39987_Marimo_RCE_detector.py -t http://10.0.0.5:2718 --timeout 15
**Opciones:**
| Bandera | Descripción | Predeterminado |
|------|-------------|---------|
| `-t`, `--target` | URL de destino única (p. ej., `http://host:2718`) | — |
| `-f`, `--file` | Archivo con URLs de destino, una por línea (se admiten comentarios con `#`) | — |
| `-o`, `--output` | Guardar resultados en archivo JSON | — |
| `--timeout` | Tiempo de espera de conexión en segundos | `10` |
| `--verify-ssl` | Habilitar verificación de certificado SSL | Deshabilitado |
| `-v`, `--verbose` | Salida detallada con información completa | Desactivado |
**Ejemplo de salida:**```
[*] CVE-2026-39987 Marimo Pre-Auth RCE Scanner
[*] Scanning 1 target(s)...
[*] NOTE: This scanner only checks for endpoint exposure.
[*] No commands are executed on target systems.
======================================================================
Target: http://10.0.0.5:2718
Scan Time: 2026-04-14T16:00:00Z
Risk Level: CRITICAL
======================================================================
Is Marimo: YES
Marimo Version: 0.19.2
/terminal/ws Open: YES — UNAUTHENTICATED
Vulnerable: YES
*** CRITICAL: Pre-authenticated RCE is possible! ***
*** An attacker can get a full PTY shell without any auth ***
Details:
- Marimo instance detected via /api/status
- Marimo version: 0.19.2
- WebSocket upgrade accepted — /terminal/ws accessible WITHOUT auth
- CONFIRMED: /terminal/ws accepts unauthenticated connections while
/ws requires auth — classic CVE-2026-39987 signature
- Version 0.19.2 <= 0.20.4 — VULNERABLE to pre-auth RCE
======================================================================
[*] Scan Complete: 1 targets scanned
[*] Marimo Instances: 1 | Vulnerable: 1 | Critical: 1
======================================================================
sudo cp CVE-2026-39987_Marimo_RCE.nse /usr/share/nmap/scripts/ sudo nmap --script-updatedb
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE
nmap -p 2718,8080,8443,443 --script CVE-2026-39987_Marimo_RCE
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE 10.0.0.0/24
nmap -p 2718 --script CVE-2026-39987_Marimo_RCE -iL targets.txt
nmap -sV -p 2718 --script CVE-2026-39987_Marimo_RCE
**Ejemplo de salida de Nmap:**```
PORT STATE SERVICE
2718/tcp open http
| CVE-2026-39987_Marimo_RCE:
| VULNERABLE:
| Marimo Pre-Auth RCE (CVE-2026-39987)
| State: VULNERABLE
| Risk level: CRITICAL
| Marimo Version: 0.19.2
| /terminal/ws: accessible without authentication
| Description:
| The Marimo /terminal/ws WebSocket endpoint accepts connections
| without authentication, enabling pre-authenticated RCE.
| An attacker can obtain a full PTY shell without any credentials.
| References:
|_ https://nvd.nist.gov/vuln/detail/CVE-2026-39987
Si tienes acceso a la instancia de Marimo:```bash
curl -s http://:2718/api/status | python3 -m json.tool
curl -s -o /dev/null -w "%{http_code}"
-H "Upgrade: websocket"
-H "Connection: Upgrade"
-H "Sec-WebSocket-Key: dGVzdC1rZXktMTIzNDU2Nzg="
-H "Sec-WebSocket-Version: 13"
http://:2718/terminal/ws
curl -s -o /dev/null -w "%{http_code}"
-H "Upgrade: websocket"
-H "Connection: Upgrade"
-H "Sec-WebSocket-Key: dGVzdC1rZXktMTIzNDU2Nzg="
-H "Sec-WebSocket-Version: 13"
http://:2718/ws
Si `/terminal/ws` devuelve 101 mientras que `/ws` devuelve 401/403, esta es la firma clásica de CVE-2026-39987.
---
## Indicadores de compromiso
Busque estas señales en su entorno:
| Indicador | Dónde comprobar | Qué buscar |
|:---|:---|:---|
| Conexiones WebSocket no autorizadas | Registros del servidor/proxy | Conexiones a `/terminal/ws` desde IPs inesperadas |
| Creación de sesiones PTY | Monitoreo de procesos | Procesos de shell inesperados generados por el servidor Marimo |
| Acceso a archivos | Registros de auditoría de archivos | Lecturas de `.env`, archivos de credenciales o claves SSH |
| Uso de credenciales | Registros de auditoría del proveedor de nube | Llamadas a la API que utilizan claves almacenadas en el entorno de Marimo |
| Transferencia de datos salientes | Monitoreo de red | Tráfico de salida inusual desde el host de Marimo |
**Comandos de investigación:**```bash
# Check for active WebSocket connections
ss -tnp | grep <MARIMO_PORT>
# Review process tree for unexpected shells
ps aux --forest | grep -A5 marimo
# Check if .env or credential files were recently accessed
stat .env
stat ~/.aws/credentials
# Review cloud provider activity logs for unauthorized access
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=<KEY_ID>
# Check for unauthorized outbound connections
netstat -tnp | grep ESTABLISHED | grep -v 127.0.0.1
Acciones inmediatas (hazlas ahora):
pip install --upgrade marimo)A corto plazo (esta semana):
.env y las variables de entorno para detectar cualquier dato sensible que pueda haber quedado expuestoA largo plazo:
Kerem Oruç — Ingeniero de Ciberseguridad