[解説] FlowiseにおけるRCE脆弱性 (CVE-2026-69264): データURIとPyodideの危険な連携
はじめに:Flowiseと今回の脆弱性
Flowiseは、LLM(大規模言語モデル)を活用したAIエージェントやチャットボットを構築するためのオープンソースプラットフォームです。直感的なUIでさまざまなノードを組み合わせてワークフローを構築できるため、多くの開発者に利用されています。
今回解説する脆弱性(GHSA-4j8x-x6v7-w9rq / CVE-2026-69264)は、Flowiseの`CSVAgent`ノードに存在する**リモートコード実行 (RCE)** の問題です。この脆弱性は、Flowiseが動作しているNode.jsホスト上で攻撃者が任意のOSコマンドを実行できてしまうという、非常に深刻な影響をもたらします。フロントエンド開発者が関わる可能性のある、データURIの処理やPyodideのようなWebAssembly技術のセキュリティリスクについて深く掘り下げていきましょう。
脆弱性の概要:なぜRCEが発生するのか?
この脆弱性の核心は、`CSVAgent`がCSVファイルを処理する際に、入力されたデータURIの一部を適切にサニタイズせず、そのままPythonのソースコードに文字列として挿入してしまう点にあります。さらに、Flowiseが利用しているPyodide(WebAssembly上でPythonを実行するライブラリ)のデフォルト設定が、このコードインジェクションを悪用してNode.js環境に脱出する経路を提供しています。
深刻度はCVSS v3.1で**9.9 (Critical)** と評価されており、認証されたユーザーが悪意のあるチャットフローを一度作成すれば、その後のトリガーは認証なしで実行される可能性があります。
技術的詳細:攻撃経路を追う
`packages/components/nodes/agents/CSVAgent/CSVAgent.ts`の`run()`メソッドにおいて、`csvFile`として与えられたデータURI(例: `data:text/csv;base64,A,BASE64_STRING,IGNORED`)を処理するロジックがあります。この処理の中で、`split(',')`と2回の`pop()`が使われ、データURIの特定のセグメント(攻撃者が制御可能なBase64部分)が抽出されます。
以下のコードがその一部です。
<pre><code>const splitDataURI = file.split(','); splitDataURI.pop(); // discards trailing filename segment base64String += splitDataURI.pop() ?? ''; // captures the segment we attack</code></pre>
ここで抽出された`base64String`が、今回の脆弱性の重要なキーとなります。攻撃者は、この部分に巧妙に細工した文字列を仕込むことができます。
抽出された`base64String`は、その後、Pythonのソースコードテンプレート内に**検証なしで直接文字列補間**されます。これにより、攻撃者はPythonの文字列リテラルを閉じて、任意のPythonコードを挿入することが可能になります。
<pre><code>const code = `import pandas as pd ... base64_string = "${base64String}" // ← line 161: interpolation sink ... json.dumps(my_dict)` dataframeColDict = await pyodide.runPythonAsync(code) // ← line 171: sink</code></pre>
この`base64_string = "${base64String}"`の部分が問題です。`base64String`が`"; import os; os.system('任意のコマンド'); #`のような形であれば、Pythonの文字列リテラルを閉じ、任意のコードを実行させ、残りのコードをコメントアウトすることができます。
FlowiseはPyodideをデフォルト設定でロードしています。Node.js環境では、Pyodide内部の`js`モジュールが`globalThis`にブリッジされ、JavaScriptの`eval`関数やトップレベルの動的`import()`が公開されます。これにより、Pyodideのサンドボックス内で実行されているPythonコードから、ホストNode.jsプロセスの機能にアクセスできてしまいます。
攻撃者は、挿入したPythonコードから以下のようにNode.jsの`fs`モジュールをインポートし、ファイル書き込みを実行できます。
<pre><code>import js await js.eval( "(async () => {" " const fs = await import('fs');" " fs.writeFileSync('proof.txt', 'pwned');" "})()" )</code></pre>
`fs`モジュールを`child_process`に置き換えれば、`cp.execSync(...)`を介して任意のOSコマンド実行が可能となります。これはPyodideのWASMサンドボックス内ではなく、ホストのNode.jsプロセス上で実行されるため、Flowiseアプリケーションが持つ権限でシステムを操作できてしまいます。
Flowiseには既存のバリデーター(`validatePythonCodeForDataFrame`や`validateCustomReadCSVFunction`)が存在しますが、これらはこのブートストラップテンプレートのコードパスには適用されません。結果として、攻撃者のインジェクションが検出されることなく、Pyodideに渡されてしまいます。
悪用シナリオと影響範囲
この脆弱性は、以下の2つのステップで悪用されます。
<ol><li>**チャットフローの作成 (Planting)**: `chatflows:create`または同等の権限を持つ認証済みユーザーが、悪意のある`csvFile`データURIを含む`CSVAgent`ノードを持つチャットフローを作成し、Flowiseに保存します。</li><li>**実行のトリガー (Trigger)**: 作成されたチャットフローが、認証なしでアクセス可能な`POST /api/v1/prediction/:id`エンドポイント経由で公開されている場合、誰でもこのエンドポイントにリクエストを送ることで、悪意のあるコードの実行をトリガーできます。Flowiseのデフォルト設定では、新規作成されたチャットフローは`apikeyid`が設定されていないため、この経路は認証なしで利用可能です。</li></ol>
これにより、Flowiseプロセスが動作しているホスト上で、**任意のOSコマンド実行**が可能になります。結果として、Flowiseのデータベース、暗号化されたクレデンシャルキーファイル、ホストのファイルシステム、およびホストがアクセス可能なネットワークリソースへの完全なアクセスを許してしまいます。
フロントエンドエンジニアが学ぶべき教訓
この脆弱性から、フロントエンドエンジニアとして以下の重要な教訓を得ることができます。
ユーザーが制御可能な入力値(今回のケースではデータURIのBase64セグメント)は、それがどのような形で利用される場合でも、必ず厳格な検証とサニタイズを行う必要があります。特に、コードの一部として解釈される可能性のある場所への文字列補間は極めて危険です。許可された文字セット(例: Base64であれば`^[A-Za-z0-9+/=]*$`)以外は拒否する、ホワイトリスト形式の検証が効果的です。
動的にコードを生成したり、文字列補間を使ってコードに値を埋め込んだりする際は、常にコードインジェクションのリスクを考慮する必要があります。可能な限り、文字列補間ではなく、APIの引数や環境変数など、データとして値を渡す安全なメカニズムを利用するべきです。
PyodideのようなWebAssemblyベースのサンドボックス環境は、その分離性によってセキュリティを確保しますが、ホストシステムとのブリッジ機能(`js`モジュールなど)がデフォルトで有効になっている場合、注意が必要です。必要ない場合は、これらの機能を無効にするなど、最小権限の原則に基づいて設定を調整することが不可欠です。
Flowiseのようなプラットフォームは、Pyodideやpandasといった多くのサードパーティライブラリに依存しています。これらのライブラリのセキュリティ設定や挙動、既知の脆弱性について常に意識し、最新の情報を追うことが重要です。
対策と緩和策
Flowiseの開発者とユーザー、それぞれに推奨される対策があります。
最も根本的な解決策は、Base64値を文字列補間ではなく、Pyodideの`globals.set` APIを使ってPython環境に渡すことです。これにより、値がPythonのソースコードとして解釈されるリスクがなくなります。
<pre><code>const pyodide = await LoadPyodide(); pyodide.globals.set('base64_string', base64String); // 文字列補間ではなくAPIで渡す const code = `import pandas as pd import base64 ... decoded_data = base64.b64decode(base64_string) ...`; dataframeColDict = await pyodide.runPythonAsync(code);</code></pre>
また、以下の防御的深層化も推奨されます。
<ul><li>`base64String`に対して厳格なBase64形式の入力検証を適用する。</li><li>Pyodideのロード時に`js`モジュールを無効にする (`loadPyodide({ jsglobals: {} })`)。</li><li>ブートストラップテンプレート自体にもPythonコードの検証を適用する。</li><li>`validateCustomReadCSVFunction`には安全なpandasリーダーのホワイトリストを適用する。</li></ul>
パッチが適用されるまでの間、ユーザーは以下の対策を講じることができます。
<ul><li>`CSVAgent`を使用するすべてのチャットフローで`chatflow.apikeyid`を設定し、`/api/v1/prediction/:id`エンドポイントへのアクセスに認証を強制する。</li><li>`chatflows:create`や`agentflows:create`などのチャットフロー作成・更新権限を信頼できるユーザーのみに制限する。</li><li>もし可能であれば、影響を受けるチャットフローの`nodeOverrides`の許可リストから`csvFile`を削除し、外部からの`csvFile`指定を防ぐ。</li></ul>
まとめ
Flowiseの`CSVAgent`におけるRCE脆弱性は、データURIの処理、Pythonソースコードへの文字列補間、そしてPyodideのNode.jsブリッジの組み合わせによって引き起こされました。この事例は、単一のコンポーネントだけでなく、複数の技術スタックが連携する際のセキュリティ設計がいかに重要であるかを浮き彫りにしています。
フロントエンドエンジニアの皆さんも、ユーザーからの入力値を扱う際には常にサニタイズと検証を徹底し、動的にコードを生成する際の潜在的なリスクを意識するよう心がけましょう。このような深刻な脆弱性から学び、より堅牢なシステム構築に貢献していきましょう。