
Kit de detecção e exploração para CVE-2026-39987, um RCE pré-autenticação em notebooks Marimo. Inclui scanner em Python e script NSE do Nmap para identificar instâncias vulneráveis por meio de verificações de endpoint WebSocket.
Uma vulnerabilidade de execução remota de código pré-autenticada no Marimo, um notebook Python de código aberto para ciência de dados e IA/ML. O endpoint WebSocket do terminal (/terminal/ws) ignora completamente a validação de autenticação, enquanto o endpoint vizinho do notebook (/ws) a aplica corretamente. Um atacante não autenticado pode se conectar a /terminal/ws e obter um shell PTY interativo completo no sistema host sem nenhuma credencial.
Explorada ativamente em menos de 10 horas após a divulgação. Atacantes roubaram credenciais AWS em menos de 3 minutos.
Afeta Marimo <= 0.20.4. Corrigido no Marimo 0.23.0.
| Campo | Detalhe |
|---|---|
| ID CVE | CVE-2026-39987 |
| Fornecedor | Projeto Marimo |
| Produto | Marimo (Notebook Python) |
| Versões Afetadas | <= 0.20.4 |
| CVSS v3.1 | 9.3 (Crítico) |
| CWE | CWE-306 — Autenticação Ausente para Função Crítica |
| Vetor de Ataque | Rede |
| Autenticação | Nenhuma necessária |
| Interação do Usuário | Nenhuma |
| Maturidade do Exploit | Explorado ativamente na natureza |
| Tempo até a Exploração | ~10 horas após a divulgação |
| Corrigido Em | Marimo 0.23.0 |
O Marimo é um notebook Python reativo de código aberto, projetado como uma alternativa moderna ao Jupyter. Ele é construído para ciência de dados, experimentação em IA/ML e análise interativa de dados. Seus principais recursos incluem rastreamento automático de dependências, execução reproduzível e uma experiência de desenvolvimento mais limpa em comparação aos notebooks tradicionais.
O Marimo está ganhando rápida adoção na comunidade Python e de IA/ML, especialmente entre profissionais que desejam fluxos de trabalho de notebook mais estruturados do que o Jupyter oferece.
Como todos os ambientes de notebook, as instâncias do Marimo normalmente têm acesso a recursos sensíveis: credenciais de nuvem (AWS, GCP, Azure), strings de conexão de banco de dados, chaves de API para serviços de IA (OpenAI, Anthropic, etc.) e acesso à rede interna. Diferentemente de aplicações web tradicionais, os notebooks são projetados para executar código arbitrário. Esse é o seu propósito central.
Essa combinação torna qualquer bypass de autenticação em um ambiente de notebook particularmente devastador.``` 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álise Aprofundada da Vulnerabilidade
### Os Dois Endpoints WebSocket
O servidor do Marimo implementa vários endpoints WebSocket para diferentes funcionalidades. A diferença crítica entre os dois endpoints principais é a presença (ou ausência) de uma verificação de autenticação:```
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!
O endpoint do notebook (/ws) chama corretamente validate_auth() para verificar a identidade do usuário antes de aceitar conexões WebSocket. Este é o comportamento de segurança esperado.
O endpoint do terminal (/terminal/ws) apenas verifica se o servidor está em modo de execução e se a plataforma suporta a funcionalidade de terminal. Ele nunca chama validate_auth(). Após passar por essas verificações básicas, ele aceita a conexão e cria uma sessão completa de PTY (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
Este é o **CWE-306: Ausência de Autenticação para Função Crítica**. O endpoint mais perigoso do servidor (aquele que fornece um shell interativo) não possui autenticação alguma.
### O Ataque: De Advisory a Chaves AWS em 3 Minutos