
Joomla 6.1.3 - عالي - عن بُعد - ACL - POST /api/v1/content/articles يتجاهل ACL الخاص بكل فئة
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · joomla-articles-catid
المقال التقني: https://abraxaslabs.tech/research/joomla-articles-catid
الفئة: صلاحية متبقية الوصول: عن بُعد
Joomla CMS 6.1.3 (0f2d4de) - Open Source Matters
دالة ArticleController::allowAdd الخاصة بالمسؤول تتحقق من com_content.category.{catid}. الملف api/components/com_content/src/Controller/ArticlesController.php لا يحتوي على allowAdd. بعد ذلك تستدعي ApiController::add() الدالة allowAdd() دون أي بيانات، لذا لا يدخل حقل catid في JSON إلى عملية التحقق أبدًا. الدالة الأساسية allowAdd تُرجع true إذا كان المستخدم قادرًا على الإنشاء في أي فئة.
مستخدم API من فئة Manager، مُمنوح core.login.api وصلاحية الإنشاء على فئة واحدة، يمكنه إرسال مقال عبر POST إلى فئة محظور عليه الوصول إليها في واجهة المستخدم.
| المعرّف | لا يوجد CVE بعد |
| الفئة | صلاحية متبقية / تجاوز التفويض |
| الوصول | عن بُعد (رمز API مع core.login.api) |
| CWE | CWE-863 |
| CVSS | متوسط: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N |
| المنتج | Joomla CMS |
| المتأثر | 6.1.3 (0f2d4de4729eb951e0878fec98c7990da07915b2)، POST /api/index.php/v1/content/articles |
| المصادقة | رمز API لـ Manager (المجموعة 6) مع core.login.api وcom_content core.manage وcore.create على فئة واحدة فقط. بدون Super User. القيمة الافتراضية لـ core.login.api مخصصة لـ Super User فقط. |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1 فقط |
استخدام رمز API لمستخدم غير Super User يُسمح له بالإنشاء في فئة واحدة للنشر في فئات محظور عليه الوصول إليها. لا يصبح Super User نتيجة هذه الصلاحية المتبقية. هذا نشر غير مصرّح به، وليس استيلاءً على الموقع ولا قراءة لمقالات مخفية.
| الصلاحية | الطريقة / المسار | النوع | مُثبت مختبريًا أو مُستنتج |
|---|---|---|---|
| إنشاء مقال في تصنيف مرفوض | POST /api/index.php/v1/content/articles مع JSON catid للتصنيف B | سلامة البيانات | مُثبت مختبريًا. HTTP 200، #__content id=1، catid=9 (Press)، العنوان poc-art-JOOMLA-ARTICLES-CATID-WITNESS |
| زرع نسخة منشورة في Official notices / Press بينما المسموح هو المسودات فقط | نفس طلب POST، state=1 | سلامة البيانات | مُثبت مختبريًا للإنشاء في Press المرفوض. مُستنتج لأي أصل تصنيف آخر مع رفض core.create |
| تعديل مقال موجود عبر الـ API | PATCH /api/index.php/v1/content/articles/{id} | لا ينطبق | مُستنتج أنه مغلق لهذا المدير (Manager). تتطلب allowEdit الأساسية صلاحية core.edit للمكوّن؛ قواعد com_content المخبرية تمنح core.edit للمؤلف (4)، وليس للمدير (6). لم يُجرِ المختبر طلب PATCH |
| حذف مقال عبر الـ API | DELETE /api/index.php/v1/content/articles/{id} | لا ينطبق | مُستنتج أنه مغلق. تتطلب allowDelete الأساسية صلاحية core.delete للمكوّن. لم يُجرِ المختبر طلب DELETE |
| حفظ مقال في التصنيف المسموح A من لوحة الإدارة | POST ... task=article.save مع jform[catid]=A | مقصود | مُثبت مختبريًا مفتوح كما هو مقصود. HTTP 200، غير مرفوض |
| حفظ مقال في التصنيف المرفوض B من لوحة الإدارة | نفس المهمة، jform[catid]=B | لا ينطبق | مُثبت مختبريًا مغلق. HTTP 200 مع "not permitted" / create-not-permitted (واجهة المستخدم allowAdd على com_content.category.B) |
استهداف الـ API باستخدام مدير (Manager) افتراضي (بدون core.login.api) | رمز Bearer / X-Joomla-Token | لا ينطبق | مُستنتج أنه مغلق. القيمة الافتراضية لـ core.login.api هي Super User (المجموعة 8) فقط. يمنحه هذا المختبر للمجموعة 6 أولًا |
| نفس الثغرة في contacts / banners / newsfeeds | POST /api/index.php/v1/contacts (والنظائر) مع catid خارجي | سلامة البيانات | مُستنتج. تفتقر وحدات تحكم الـ API تلك أيضًا إلى allowAdd. لم يختبرها المختبر |
| تنفيذ أوامر عن بُعد (RCE) عبر Super User / المثبّت | لا ينطبق | لا ينطبق | مغلق. يبقى المدير مديرًا |
ثبّتُّ إصدار 6.1.3 بعد موجة JSST في 2026-07/08. تتطلب واجهة Groups API بالفعل صلاحية core.admin. وتتطلب خدمة Media webservice بالفعل صلاحية core.edit على override. أما Articles API فلا تزال تمر عبر ApiController وتعتبر "الإنشاء في أي تصنيف" كافيًا لكل catid.
public function add()
{
if (!$this->allowAdd()) {
throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
}
$recordId = $this->save();
$this->displayItem($recordId);
}
فحص القاعدة:
protected function allowAdd($data = [])
{
$user = $this->app->getIdentity();
if (!$user->authorise('core.manage', $this->option)) {
return false;
}
return $user->authorise('core.create', $this->option)
|| \count($user->getAuthorisedCategories($this->option, 'core.create'));
}
واجهة المستخدم للمسؤول تعتمد بالفعل على catid:
protected function allowAdd($data = [])
{
$categoryId = ArrayHelper::getValue(
$data,
'catid',
$this->input->getInt('filter_category_id'),
'int'
);
if ($categoryId) {
return $this->app->getIdentity()->authorise(
'core.create',
'com_content.category.' . $categoryId
);
}
return parent::allowAdd();
}
المسارات الخاطئة المسجّلة مسبقًا: قراءة استجابة HTTP 200 من الواجهة على أنها سماح (رفض B هو جسم "not permitted")؛ اعتبار المكوّن core.create هو الثغرة (مدير المختبر لا يملكه؛ فئة سماح واحدة تكفي لعدّ getAuthorisedCategories الأساسي)؛ مدير قياسي بدون core.login.api. المسرح: reverse shell. أداة الحكم (oracle) هي استجابة API 200 إلى Press مع سماح الواجهة على Drafts ورفض الواجهة على Press.
بقايا نفس التثبيت: levels API، menu items API، media override.
python3 joomla-articles-catid-Abraxas-Labs.py
أو
cd lab
./run.sh
استهدف فقط http://127.0.0.1:19550. الصورة joomla:6.1.3-php8.3-apache بالإضافة إلى mariadb:11.4.
SUCCESS joomla-articles-catid api-http=200 article-id=1 catid=9 cat-a=8 cat-b=9 ui-a=200 ui-b=200 JOOMLA-ARTICLES-CATID-WITNESS
article-id هو القيمة التالية للتزايد التلقائي لـ #__content (1 عند تثبيت نظيف لـ 6.1.3). ui-a / ui-b تُرجعان HTTP 200؛ رفض B هو نص واجهة المستخدم "not permitted"، وليس حالة 403.
تجاوز allowAdd (وallowEdit) في api/components/com_content/src/Controller/ArticlesController.php بالطريقة نفسها المتبعة في ArticleController الخاص بالمسؤول: تحقق من core.create على com_content.category.{catid}. مرّر JSON الطلب إلى ApiController::add() حتى يصل catid فعليًا إلى ذلك التحقق. طبّق النمط نفسه على contacts وbanners وnewsfeeds إذا بقيت تلك المتحكمات على allowAdd الافتراضي.