
Joomla 6.1.3 - Высокий - Удалённый - ACL - POST /api/v1/content/articles игнорирует ACL для отдельных категорий
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · 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() без данных, поэтому JSON-поле catid никогда не попадает в проверку. Базовый allowAdd возвращает true, если пользователь может создавать в любой категории.
Пользователь API класса Manager, которому выданы core.login.api и право создания в одной категории, может отправить статью POST-запросом в категорию, в которой ему отказано в UI.
| ID | 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, имея право только на Drafts | Тот же POST, state=1 | целостность | Доказано в лаборатории создание в запрещённой Press. Предположено для любого другого ресурса категории с запрещённым core.create |
| Редактировать существующую статью через API | PATCH /api/index.php/v1/content/articles/{id} | н/д | Предположительно закрыто для этого Manager. Базовый allowEdit требует core.edit компонента; правила com_content в лаборатории дают core.edit для Author (4), а не Manager (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 из административной панели | Тот же task, jform[catid]=B | н/д | Доказано в лаборатории закрыто. HTTP 200 с "not permitted" / create-not-permitted (UI 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 / installer | н/д | н/д | Закрыто. Manager остаётся Manager |
Я зафиксировал 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'));
}
Административный UI уже опирается на 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 UI как разрешения (отказ для B — это тело "not permitted"); рассмотрение core.create компонента как дыры (у Manager в лаборатории его нет; разрешения в одной категории достаточно для подсчёта в базовом getAuthorisedCategories); стандартный Manager без core.login.api. Театр: reverse shell. Оракул — API 200 в Press плюс разрешение UI для Drafts плюс отказ UI для 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" в UI, а не статус 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.