
IDOR + XSS almacenado mediante Broken Object-Level Authorization en JoomGallery
JoomGallery ≤ 4.3.0 — Un usuario con rol Editor secuestra cualquier imagen de la galería y almacena una carga XSS, lo que permite tomar el control de la sesión del administrador
UserimageController::save() en JoomGallery comprueba checkACL('edit', ...) en lugar de checkACL('edit.own', ...). Un usuario con rol Editor puede hacer POST a task=userimage.save&id=N para cualquier imagen, sin importar la propiedad (IDOR — CWE-639). Debido a que el rol Editor posee core.edit globalmente, la comprobación de autorización se supera para cada ID de imagen del sitio, incluidas las imágenes propiedad de administradores.
Combinado con la ausencia de la llamada a $this->escape() en la plantilla de imagen del frontend, un Editor puede almacenar una carga XSS en el título de cualquier imagen — incluidas las imágenes propiedad de administradores — provocando la ejecución de JavaScript en el navegador de cada visitante. Esto permite el secuestro total de la sesión del administrador y el compromiso de todo el sitio.
| COMPONENTE | VULNERABLE | PROBADO EN | CORREGIDO |
|---|---|---|---|
| JoomGallery (com_joomgallery) | 4.0.0 – 4.3.0 | Joomla 5.4.7 + JoomGallery 4.3.0-stable (PHP 8.2 / Apache) | 4.4.0 |
Tipo: Autorización rota a nivel de objeto / IDOR (CWE-639) encadenada con Cross-Site Scripting Almacenado (CWE-79) Autenticación requerida: Cuenta de Editor con privilegios bajos
Archivo: components/com_joomgallery/src/Controller/UserimageController.php
La acción save() realiza una comprobación ACL usando el permiso edit en lugar de edit.own. El permiso edit se otorga globalmente a todos los usuarios con rol Editor, por lo que la comprobación se supera para cualquier ID de imagen, independientemente de quién la haya creado.
USERIMAGECONTROLLER.PHP — CÓDIGO VULNERABLE (LÍNEA 145)
// Vulnerable
if (!$this->checkACL('edit', 'image', $recordId, $parent_id, true)) { ... }
Debido a que core.edit lo posee globalmente el grupo Editor, la condición se evalúa como false para cualquier ID de imagen, otorgando acceso de escritura sin restricciones. En un guardado exitoso, el modelo además actualiza created_by al ID de usuario del atacante, transfiriendo silenciosamente la propiedad de la imagen al atacante.
Archivo: components/com_joomgallery/tmpl/image/default.php
La plantilla de imagen del frontend imprime $this->item->title sin codificarlo en HTML en el contexto del atributo alt. El filtro STRING de JInput de Joomla no elimina las comillas dobles, por lo que una carga que contenga " sale del atributo e inyecta controladores de eventos arbitrarios.
DEFAULT.PHP — CÓDIGO VULNERABLE (LÍNEAS 64, 78)
// Vulnerable
item->title; ?>" ...>
La carga abc" onmouseover="alert(document.domain);" x=" se almacena en jos_joomgallery.title y se inyecta sin procesar en el atributo HTML en cada renderizado de la página. No se produce ninguna sanitización en la capa de almacenamiento ni en la de presentación.
task=userimage.save&id=3 con la carga XSS en jform[title]. La comprobación ACL se supera (core.edit, no edit.own). El servidor devuelve HTTP 303 — no 403.created_by se transfiere al ID de usuario del atacante.jos_joomgallery.title.alt="abc" onmouseover="alert(document.domain);". El XSS se ejecuta. La sesión del administrador queda capturada → compromiso total del sitio.El administrador crea admin_image mediante el backend de JoomGallery (Joomla 5.4.7). La imagen está Publicada, Aprobada y es propiedad de Administrador

GET /index.php/component/users/login — la respuesta JSON contiene "csrf.token":"a68c2b3a...". El token se captura para el POST de inicio de sesión posterior.

joomla_user_state=logged_inPOST /index.php/component/users/login con el token CSRF y las credenciales del Editor. Respuesta: HTTP 303 y Set-Cookie: joomla_user_state=logged_in. La cookie de sesión queda capturada.

GET /index.php?option=com_joomgallery con la cookie de sesión. La respuesta contiene un nuevo "csrf.token":"2d96934b..." para usar en la solicitud de guardado.

El Editor hace POST a option=com_joomgallery&task=userimage.save&id=3 con jform[title] establecido en:
abc" onmouseover="alert(document.domain);" x="
El servidor devuelve HTTP 303 (no 403), confirmando el IDOR. La cabecera Location muestra la carga XSS en la URL de redirección, confirmando que el título se aceptó y guardó.

El backend de JoomGallery muestra que la imagen ID=3 ahora tiene como Propietario: Editor User. El campo created_by se actualizó silenciosamente en la base de datos durante el guardado no autorizado.

La consulta SQL sobre jos_joomgallery confirma que la carga XSS está almacenada — " se almacena como una comilla doble cruda, no como ". No se produjo ninguna sanitización en la capa de almacenamiento.

Cualquier usuario que visite /index.php/component/joomgallery/gallery activa la carga. El diálogo alert() del navegador confirma la ejecución de JavaScript en el origen de la víctima (document.domain).

created_by al atacante, alterando permanentemente la pista de auditoría.alt puede exfiltrar la cookie de sesión del administrador, otorgando al atacante acceso total al backend y control sobre toda la instalación de Joomla.S:C), cruzando el límite de confianza entre la sesión de privilegios bajos del atacante y la sesión de privilegios altos de la víctima.