
Joomla 6.1.3 - Élevé - À distance - ACL - POST /api/v1/content/articles ignore les ACL par catégorie
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · joomla-articles-catid
Write-up : https://abraxaslabs.tech/research/joomla-articles-catid
Classe : Résidu de privilège Portée : Distante
Joomla CMS 6.1.3 (0f2d4de) - Open Source Matters
La méthode administrateur ArticleController::allowAdd vérifie com_content.category.{catid}. api/components/com_content/src/Controller/ArticlesController.php ne possède pas de allowAdd. ApiController::add() appelle ensuite allowAdd() sans données, de sorte que le catid JSON n'entre jamais dans la vérification. La méthode allowAdd de base renvoie vrai si l'utilisateur peut créer dans n'importe quelle catégorie.
Un utilisateur API de classe Manager, disposant de core.login.api et du droit de création sur une seule catégorie, peut publier un article par POST dans une catégorie où il est refusé dans l'interface.
| ID | pas encore de CVE |
| Classe | Résidu de privilège / contournement d'autorisation |
| Portée | Distante (jeton API avec core.login.api) |
| CWE | CWE-863 |
| CVSS | Moyen : 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N |
| Produit | Joomla CMS |
| Affecté | 6.1.3 (0f2d4de4729eb951e0878fec98c7990da07915b2), POST /api/index.php/v1/content/articles |
| Auth | Jeton API pour Manager (groupe 6) avec core.login.api, com_content core.manage, et core.create sur une seule catégorie. Pas de Super User. Par défaut, core.login.api est réservé au Super User. |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Utiliser un jeton API non-Super-User autorisé à créer dans une catégorie pour publier dans des catégories où il est banni. Il ne devient pas Super User grâce à ce résidu. Il s'agit d'une publication non autorisée, et non d'une prise de contrôle du site ni d'une lecture d'articles masqués.
| Privilège | Méthode / chemin | Type | Prouvé en laboratoire ou déduit |
|---|---|---|---|
| Créer un article dans une catégorie à laquelle il est refusé | POST /api/index.php/v1/content/articles JSON catid de B | intégrité | Prouvé en laboratoire. HTTP 200, #__content id=1, catid=9 (Press), titre poc-art-JOOMLA-ARTICLES-CATID-WITNESS |
| Placer une copie publiée dans Official notices / Press alors que seuls les brouillons sont autorisés | Même POST, state=1 | intégrité | Prouvé en laboratoire : création dans Press refusée. Déduit pour toute autre ressource de catégorie avec core.create refusé |
| Modifier un article existant via l'API | PATCH /api/index.php/v1/content/articles/{id} | n/a | Déduit comme fermé pour ce Manager. La base allowEdit nécessite le composant core.edit ; les règles com_content du laboratoire accordent core.edit à Author (4), pas à Manager (6). Le laboratoire n'a pas effectué de PATCH |
| Supprimer un article via l'API | DELETE /api/index.php/v1/content/articles/{id} | n/a | Déduit comme fermé. La base allowDelete nécessite le composant core.delete. Le laboratoire n'a pas effectué de DELETE |
| Enregistrer un article dans la catégorie autorisée A depuis l'administrateur | POST ... task=article.save jform[catid]=A | prévu | Prouvé en laboratoire ouvert comme prévu. HTTP 200, non refusé |
| Enregistrer un article dans la catégorie refusée B depuis l'administrateur | Même tâche, jform[catid]=B | n/a | Prouvé en laboratoire fermé. HTTP 200 avec « not permitted » / create-not-permitted (UI allowAdd sur com_content.category.B) |
Appeler l'API avec un Manager standard (sans core.login.api) | Jeton Bearer / X-Joomla-Token | n/a | Déduit comme fermé. Par défaut, core.login.api est réservé à Super User (groupe 8). Ce laboratoire l'accorde d'abord au groupe 6 |
| Même faille sur contacts / banners / newsfeeds | POST /api/index.php/v1/contacts (et équivalents) avec un catid étranger | intégrité | Déduit. Ces contrôleurs API manquent également de allowAdd. Le laboratoire ne les a pas testés |
| RCE Super User / installateur | n/a | n/a | Fermé. Le Manager reste Manager |
J'ai épinglé la version 6.1.3 après la vague JSST de 2026-07/08. L'API Groups exige déjà core.admin. Le webservice Media exige déjà core.edit sur l'override. L'API Articles passe encore par ApiController et considère « créer dans n'importe quelle catégorie » comme suffisant pour chaque catid.
public function add()
{
if (!$this->allowAdd()) {
throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
}
$recordId = $this->save();
$this->displayItem($recordId);
}
Vérification de base :
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'));
}
L'interface d'administration s'appuie déjà sur 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();
}
Faux pas déjà consignés : lire un HTTP 200 de l'UI comme une autorisation (le refus de B est le corps « not permitted ») ; traiter le composant core.create comme la faille (le Manager du lab n'en a aucun ; une seule catégorie autorisée suffit pour le décompte de base de getAuthorisedCategories) ; un Manager standard sans core.login.api. Théâtre : un reverse shell. L'oracle est un API 200 dans Press plus une autorisation UI sur Drafts plus un refus UI sur Press.
Restes du même pin : levels API, menu items API, media override.
python3 joomla-articles-catid-Abraxas-Labs.py
ou
cd lab
./run.sh
Ciblez uniquement 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 est l'auto-incrément suivant de #__content (1 sur une installation propre de 6.1.3). ui-a / ui-b renvoient HTTP 200 ; le refus de B correspond au corps « not permitted » de l'interface, et non à un statut 403.