marimo Affected by Pre-Auth Remote Code Execution via Terminal WebSocket Authentication Bypass
Corrija agora. Ela está sob exploração confirmada pelo CISA e tem exploit funcional público.
Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.
Resumo
O endpoint WebSocket /terminal/ws do marimo (notebook Python reativo) aceita conexões sem qualquer verificação de autenticação, enquanto o endpoint /ws equivalente valida corretamente o token de acesso. Isso permite que um atacante não autenticado abra uma sessão PTY completa e execute comandos arbitrários no host — em containers Docker padrão, como root. O impacto real depende de o marimo estar rodando em modo 'edit' e acessível pela rede; notebooks publicados como aplicação ('run' mode) não são afetados.
Detalhamento técnico
A falha é CWE-306 (Missing Authentication for Critical Function). Em marimo/_server/api/endpoints/terminal.py, o handler do WebSocket /terminal/ws verifica apenas se a sessão está em SessionMode.EDIT e se a plataforma suporta terminal (supports_terminal()) antes de chamar websocket.accept() e, na sequência, pty.fork() para criar um shell interativo. Nenhuma chamada a validate_auth() existe nesse caminho. O endpoint /ws, por comparação, instancia um WebSocketConnectionValidator e chama validate_auth() antes de aceitar a conexão — exatamente o passo que falta no terminal.
O marimo usa o AuthenticationMiddleware do Starlette, que marca uma conexão sem credenciais como UnauthenticatedUser mas não a rejeita automaticamente; a aplicação real do controle de acesso depende de cada endpoint chamar validate_auth() ou usar o decorator @requires(). Como /terminal/ws não tem nenhum dos dois, o middleware global não protege essa rota — é uma falha de implementação pontual, não uma falha estrutural do framework de autenticação como um todo.
O atacante não controla nenhum parâmetro de entrada sofisticado: basta abrir a conexão WebSocket. A partir daí, o texto enviado é interpretado como entrada de terminal (PTY), então qualquer comando de shell é executado com os privilégios do processo do servidor marimo — root, nos exemplos de reprodução em Docker publicados no advisory.
Como é explorada
O vetor é uma conexão WebSocket direta a ws://alvo:porta/terminal/ws, sem cabeçalho de autenticação, sem token e sem interação do usuário. A complexidade é trivial: uma única conexão aceita retorna um shell PTY completo, com saída de terminal em texto simples — o PoC público do advisory apenas conecta, drena a saída inicial e envia comandos como 'id\n' para confirmar execução como root.
As pré-condições reais que a manchete 'RCE pré-autenticação' esconde: o servidor precisa estar em modo edit (marimo edit), não em modo de aplicação publicada (run mode, que não expõe esse endpoint com o mesmo efeito); e precisa estar acessível pela rede do atacante — isso cobre cenários como marimo edit --host 0.0.0.0, instâncias expostas em rede compartilhada, ou deploys em modo edit que dependem apenas do token de acesso embutido do marimo (marimo edit --token) para segurança, já que esse token nunca é checado nesse endpoint.
A CVE está no catálogo KEV da CISA (adicionada em 2026-04-23, prazo de correção 2026-05-07), confirmando exploração ativa observada. Há template Nuclei público e reportes de pesquisadores (Sysdig) descrevendo o intervalo entre divulgação e exploração em produção como inferior a 10 horas, o que indica varredura automatizada em massa assim que o detalhe técnico ficou público.
Versões
Como se proteger
A correção definitiva é atualizar para marimo 0.23.0 ou posterior, onde /terminal/ws passa a chamar validate_auth() da mesma forma que /ws, retornando WebSocketCodes.UNAUTHORIZED quando a autenticação falha ou o token é inválido.
Se a atualização não for possível de imediato, o paliativo real é de rede, não de configuração do marimo: não expor instâncias em modo edit em interfaces além de 127.0.0.1 (evitar --host 0.0.0.0 sem controle adicional), e restringir acesso à porta do marimo via firewall, VPN ou proxy autenticado que filtre o caminho /terminal/ws antes de chegar à aplicação. Rodar os notebooks apenas em modo de aplicação publicada (run mode), quando o caso de uso permitir, remove a superfície mais crítica, já que esse modo não expõe o mesmo caminho de execução de shell.
O que não funciona como mitigação: confiar no token de acesso embutido do marimo (--token) ou assumir que o AuthenticationMiddleware do Starlette protege todos os WebSockets — como o advisory demonstra, esse middleware não rejeita conexões sozinho, e o endpoint do terminal simplesmente nunca consultava o resultado da autenticação. Também não há flag de configuração documentada para desabilitar seletivamente o terminal sem aplicar o patch.
Como detectar
Em logs do servidor marimo ou de proxy/reverse-proxy na frente dele, procure handshakes WebSocket para o caminho /terminal/ws sem o parâmetro access_token esperado, ou vindos de IPs fora da faixa de administradores conhecidos. No nível de processo, monitore o processo marimo criando processos-filho via pty.fork() (novos PTYs/shells) sem correlação com uma sessão de edição legítima no navegador do operador — isso é o sinal mais direto de exploração bem-sucedida.
Como o exploit é uma única conexão WebSocket com payload textual simples (comandos de shell em texto puro), não há assinatura de rede complexa: qualquer ferramenta de scanning que teste a existência do endpoint /terminal/ws e envie um comando de teste (como 'id') é suficiente para comprometer um alvo vulnerável, e o template Nuclei público automatiza exatamente esse teste — presença desse tipo de requisição nos logs de acesso indica tentativa, não necessariamente sucesso, que só se confirma pela criação do PTY no host.