
Joomla 6.1.3 - Alta - Remota - ACL - POST /api/v1/content/articles ignora las ACL por categoría
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · joomla-articles-catid
Clase: Privilegio residual Alcance: Remoto
Joomla CMS 6.1.3 (0f2d4de) - Open Source Matters
El ArticleController::allowAdd del administrador comprueba com_content.category.{catid}. api/components/com_content/src/Controller/ArticlesController.php no tiene allowAdd. ApiController::add() entonces llama a allowAdd() sin datos, por lo que el catid del JSON nunca entra en la comprobación. El allowAdd base es verdadero si el usuario puede crear en cualquier categoría.
Un usuario de API de clase Manager, al que se le concede core.login.api y creación en una categoría, puede publicar un artículo mediante POST en una categoría en la que está denegado en la UI.
| ID | aún sin CVE |
| Clase | Privilegio residual / omisión de autorización |
| Alcance | Remoto (token de API con core.login.api) |
| CWE | CWE-863 |
| CVSS | Medio: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N |
| Producto | Joomla CMS |
| Afectado | 6.1.3 (0f2d4de4729eb951e0878fec98c7990da07915b2), POST /api/index.php/v1/content/articles |
| Autenticación | Token de API para Manager (grupo 6) con core.login.api, com_content core.manage y core.create en una sola categoría. Sin Super User. El core.login.api predeterminado es solo para Super User. |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Usar un token de API que no es de Super User y que puede crear en una categoría para publicar en categorías en las que está prohibido. No se convierten en Super User a partir de este residuo. Esto es publicación no autorizada, no una toma de control del sitio ni una lectura de artículos ocultos.
| Privilegio | Método / ruta | Tipo | Probado en laboratorio o inferido |
|---|---|---|---|
| Crear un artículo en una categoría en la que está denegado | POST /api/index.php/v1/content/articles JSON catid de B | integridad | Probado en laboratorio. HTTP 200, #__content id=1, catid=9 (Press), título poc-art-JOOMLA-ARTICLES-CATID-WITNESS |
| Colocar una copia publicada en Official notices / Press mientras solo se permiten Drafts | Mismo POST, state=1 | integridad | Creación probada en laboratorio en Press denegada. Inferido para cualquier otro recurso de categoría con core.create denegado |
| Editar un artículo existente mediante la API | PATCH /api/index.php/v1/content/articles/{id} | n/a | Inferido cerrado para este Manager. El allowEdit base necesita el core.edit del componente; las reglas de com_content del laboratorio dan core.edit a Author (4), no a Manager (6). El laboratorio no hizo PATCH |
| Eliminar un artículo mediante la API | DELETE /api/index.php/v1/content/articles/{id} | n/a | Inferido cerrado. El allowDelete base necesita el core.delete del componente. El laboratorio no hizo DELETE |
| Guardar un artículo en la categoría permitida A desde el administrador | POST ... task=article.save jform[catid]=A | previsto | Probado en laboratorio abierto según lo previsto. HTTP 200, no denegado |
| Guardar un artículo en la categoría denegada B desde el administrador | Misma tarea, jform[catid]=B | n/a | Probado en laboratorio cerrado. HTTP 200 con "not permitted" / create-not-permitted (UI allowAdd en com_content.category.B) |
Acceder a la API con un Manager estándar (sin core.login.api) | Token Bearer / X-Joomla-Token | n/a | Inferido cerrado. El core.login.api predeterminado es solo para Super User (grupo 8). Este laboratorio lo concede primero al grupo 6 |
| Misma brecha en contacts / banners / newsfeeds | POST /api/index.php/v1/contacts (y similares) con catid ajeno | integridad | Inferido. Esos controladores de API también carecen de allowAdd. El laboratorio no los ejercitó |
| RCE de Super User / instalador | n/a | n/a | Cerrado. Manager sigue siendo Manager |
Fijé 6.1.3 tras la oleada de JSST de 2026-07/08. La API de Groups ya requiere core.admin. El webservice de Media ya requiere core.edit en override. La API de Articles todavía cae a través de ApiController y trata "crear en cualquier categoría" como suficiente para cada catid.
public function add()
{
if (!$this->allowAdd()) {
throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403);
}
$recordId = $this->save();
$this->displayItem($recordId);
}
Comprobación 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'));
}
La UI del administrador ya se basa en 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();
}
Pistas falsas ya registradas: leer el HTTP 200 de la UI como permitido (la denegación de B es el cuerpo "not permitted"); tratar el core.create del componente como el agujero (el Manager del laboratorio no tiene ninguno; permitir una categoría es suficiente para el recuento base de getAuthorisedCategories); un Manager estándar sin core.login.api. Teatro: una reverse shell. El oráculo es API 200 en Press más UI permitida en Drafts más UI denegada en Press.
Residuos del mismo pin: levels API, menu items API, media override.
python3 joomla-articles-catid-Abraxas-Labs.py
o
cd lab
./run.sh
Objetivo solo http://127.0.0.1:19550. Imagen joomla:6.1.3-php8.3-apache más 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 es el siguiente autoincremento de #__content (1 en una instalación limpia de 6.1.3). ui-a / ui-b son HTTP 200; la denegación de B es el cuerpo "not permitted" de la UI, no un estado 403.
Sobrescribir allowAdd (y allowEdit) en api/components/com_content/src/Controller/ArticlesController.php de la misma forma que el ArticleController del administrador: comprobar core.create en com_content.category.{catid}. Pasar el JSON de la petición a ApiController::add() para que catid llegue realmente a esa comprobación. Aplicar el mismo patrón a contacts, banners y newsfeeds si esos controladores permanecen en el allowAdd predeterminado.