
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
Classe : Reliquat de privilège Portée : Distante
Joomla CMS 6.1.3 (0f2d4de) - Open Source Matters
Le contrôleur administrateur ArticleController::allowAdd vérifie com_content.category.{catid}. api/components/com_content/src/Controller/ArticlesController.php n'a pas de allowAdd. ApiController::add() appelle ensuite allowAdd() sans données, donc le catid JSON n'entre jamais dans la vérification. Le allowAdd de base est 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 catégorie, peut publier un article via POST dans une catégorie qui lui est refusée dans l'interface.
| ID | pas encore de CVE |
| Classe | Reliquat 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 |
| Authentification | 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 |
| Labo | 127.0.0.1 uniquement |
Utiliser un jeton API non-Super-User qui peut créer dans une catégorie pour publier dans des catégories qui lui sont interdites. Il ne devient pas Super User à partir de ce reliquat. Il s'agit d'une publication non autorisée, pas d'une prise de contrôle du site ni d'une lecture d'articles cachés.
| Privilège | Méthode / chemin | Type | Prouvé en labo ou déduit |
|---|---|---|---|
| Créer un article dans une catégorie qui lui est refusée | POST /api/index.php/v1/content/articles JSON catid de B | intégrité | Prouvé en labo. 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 qu'il n'est autorisé qu'aux Brouillons | Même POST, state=1 | intégrité | Prouvé en labo pour la 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. Le allowEdit de base nécessite core.edit sur le composant ; les règles com_content du labo donnent core.edit à Author (4), pas à Manager (6). Le labo n'a pas testé PATCH |
| Supprimer un article via l'API | DELETE /api/index.php/v1/content/articles/{id} | n/a | Déduit comme fermé. Le allowDelete de base nécessite core.delete sur le composant. Le labo n'a pas testé 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 labo 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 labo fermé. HTTP 200 avec « not permitted » / create-not-permitted (UI allowAdd sur com_content.category.B) |
Solliciter 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é au Super User (groupe 8). Ce labo l'accorde d'abord au groupe 6 |
| Même faille sur contacts / banners / newsfeeds | POST /api/index.php/v1/contacts (et similaires) avec un catid étranger | intégrité | Déduit. Ces contrôleurs API n'ont pas non plus de allowAdd. Le labo ne les a pas exercés |
| RCE Super User / installateur | n/a | n/a | Fermé. Le Manager reste Manager |
J'ai épinglé 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 administrateur se base 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();
}
Fausses pistes déjà consignées : lire un HTTP 200 de l'interface comme une autorisation (le refus de B est le corps « not permitted ») ; considérer core.create du composant comme la faille (le Manager du labo n'en a aucun ; une seule catégorie autorisée suffit pour le comptage de base 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.
Autres reliquats du même épinglage : levels API, menu items API, media override.
python3 joomla-articles-catid-Abraxas-Labs.py
ou
cd lab
./run.sh
Cible 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 le prochain autoincrement de #__content (1 sur une installation 6.1.3 propre). ui-a / ui-b sont des HTTP 200 ; le refus de B est le corps « not permitted » de l'interface, pas un statut 403.