
Joomla 6.1.3 – Hoch – Remote – ACL – POST /api/v1/content/articles ignoriert ACL pro Kategorie
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · joomla-articles-catid
Write-up: https://abraxaslabs.tech/research/joomla-articles-catid
Klasse: Übrig gebliebenes Privileg Reichweite: Remote
Joomla CMS 6.1.3 (0f2d4de) - Open Source Matters
Die Administrator-Methode ArticleController::allowAdd prüft com_content.category.{catid}. api/components/com_content/src/Controller/ArticlesController.php besitzt kein allowAdd. ApiController::add() ruft anschließend allowAdd() ohne Daten auf, sodass die JSON-catid nie in die Prüfung gelangt. Das Basis-allowAdd ist wahr, wenn der Benutzer in irgendeiner Kategorie erstellen darf.
Ein API-Benutzer der Manager-Klasse, dem core.login.api und das Erstellen in einer Kategorie gewährt wurde, kann einen Artikel per POST in eine Kategorie einstellen, für die ihm in der UI die Berechtigung verweigert wird.
| ID | noch keine CVE |
| Klasse | Übrig gebliebenes Privileg / Umgehung der Autorisierung |
| Reichweite | Remote (API-Token mit core.login.api) |
| CWE | CWE-863 |
| CVSS | Mittel: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N |
| Produkt | Joomla CMS |
| Betroffen | 6.1.3 (0f2d4de4729eb951e0878fec98c7990da07915b2), POST /api/index.php/v1/content/articles |
| Auth | API-Token für Manager (Gruppe 6) mit core.login.api, com_content core.manage und core.create in einer Kategorie. Kein Super User. Standardmäßig ist core.login.api nur für Super User verfügbar. |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Mit einem API-Token eines Nicht-Super-Users, der in einer Kategorie erstellen darf, in Kategorien veröffentlichen, für die er gesperrt ist. Durch dieses übrig gebliebene Privileg wird er nicht zum Super User. Dies ist eine unbefugte Veröffentlichung, keine Übernahme der Website und kein Auslesen versteckter Artikel.
| Berechtigung | Methode / Pfad | Art | Im Labor nachgewiesen oder abgeleitet |
|---|---|---|---|
| Einen Artikel in einer Kategorie erstellen, für die ihnen die Berechtigung verweigert wird | POST /api/index.php/v1/content/articles JSON catid von B | Integrität | Im Labor nachgewiesen. HTTP 200, #__content id=1, catid=9 (Press), Titel poc-art-JOOMLA-ARTICLES-CATID-WITNESS |
| Eine veröffentlichte Kopie in Official notices / Press platzieren, obwohl nur Entwürfe erlaubt sind | Derselbe POST, state=1 | Integrität | Im Labor nachgewiesen: Erstellung in die verweigerte Kategorie Press. Abgeleitet für jedes andere Kategorie-Asset, bei dem core.create verweigert wird |
| Einen bestehenden Artikel über die API bearbeiten | PATCH /api/index.php/v1/content/articles/{id} | n/a | Abgeleitet: geschlossen für diesen Manager. Das Basis-allowEdit benötigt die Komponentenberechtigung core.edit; die com_content-Regeln im Labor geben core.edit an Author (4), nicht an Manager (6). Im Labor wurde kein PATCH ausgeführt |
| Einen Artikel über die API löschen | DELETE /api/index.php/v1/content/articles/{id} | n/a | Abgeleitet: geschlossen. Das Basis-allowDelete benötigt die Komponentenberechtigung core.delete. Im Labor wurde kein DELETE ausgeführt |
| Einen Artikel aus dem Administrator in die erlaubte Kategorie A speichern | POST ... task=article.save jform[catid]=A | beabsichtigt | Im Labor nachgewiesen: offen wie beabsichtigt. HTTP 200, nicht verweigert |
| Einen Artikel aus dem Administrator in die verweigerte Kategorie B speichern | Dieselbe Task, jform[catid]=B | n/a | Im Labor nachgewiesen: geschlossen. HTTP 200 mit „not permitted“ / create-not-permitted (UI allowAdd auf com_content.category.B) |
Die API mit einem Standard-Manager ansprechen (ohne core.login.api) | Bearer-Token / X-Joomla-Token | n/a | Abgeleitet: geschlossen. Standardmäßig ist core.login.api nur für Super User (Gruppe 8) aktiviert. Dieses Labor gewährt es zuerst Gruppe 6 |
| Dieselbe Lücke bei contacts / banners / newsfeeds | POST /api/index.php/v1/contacts (und verwandte Endpunkte) mit fremder catid | Integrität | Abgeleitet. Auch diesen API-Controllern fehlt allowAdd. Im Labor wurden sie nicht getestet |
| Super-User-/Installer-RCE | n/a | n/a | Geschlossen. Manager bleibt Manager |
Ich habe 6.1.3 nach der JSST-Welle von 2026-07/08 festgelegt. Die Groups-API erfordert bereits core.admin. Der Media-Webservice erfordert bereits core.edit für Overrides. Die Articles-API fällt weiterhin durch ApiController und betrachtet „in beliebiger Kategorie erstellen“ als ausreichend für jede catid.
public function add()
{
if (!$this->allowAdd()) {
throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
}
$recordId = $this->save();
$this->displayItem($recordId);
}
Basisprüfung:
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'));
}
Die Administrator-Oberfläche richtet sich bereits nach 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();
}
Bereits erfasste Fehlversuche: UI-HTTP 200 als Allow lesen (die Ablehnung von B ist der „not permitted“-Body); die Komponente core.create als Lücke behandeln (der Lab-Manager hat keine; eine Kategorie-Allow reicht für den Basis-Zähler von getAuthorisedCategories); ein Standard-Manager ohne core.login.api. Theater: eine Reverse Shell. Das Orakel ist API 200 in Press plus UI-Allow bei Drafts plus UI-Deny bei Press.
Übrig gebliebene Pins derselben Art: levels API, menu items API, media override.
python3 joomla-articles-catid-Abraxas-Labs.py
oder
cd lab
./run.sh
Ziel ist ausschließlich http://127.0.0.1:19550. Image joomla:6.1.3-php8.3-apache plus 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 ist der nächste #__content-Autoincrement (1 bei einer sauberen 6.1.3-Installation). ui-a / ui-b sind HTTP 200; die Verweigerung von B ist der UI-Text „not permitted“, kein 403-Status.