
IDOR + Stored XSS via Broken Object-Level Authorization in JoomGallery
JoomGallery ≤ 4.3.0 — Editor-Role User Hijacks Any Gallery Image and Stores XSS Payload, Enabling Admin Session Takeover
UserimageController::save() in JoomGallery checks checkACL('edit', ...) instead of checkACL('edit.own', ...). An Editor-role user can POST to task=userimage.save&id=N for any image regardless of ownership (IDOR — CWE-639). Because Editor-role carries core.edit globally, the authorization check passes for every image ID on the site, including images owned by administrators.
Combined with a missing $this->escape() call in the frontend image template, an Editor can store an XSS payload in any image title — including admin-owned images — causing JavaScript execution in every visitor's browser. This enables full admin session hijack and site-wide compromise.
| COMPONENT | VULNERABLE | TESTED ON | FIXED |
|---|---|---|---|
| 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 |
Type: Broken Object-Level Authorization / IDOR (CWE-639) chained with Stored Cross-Site Scripting (CWE-79) Authentication required: Low-privilege Editor account
File: components/com_joomgallery/src/Controller/UserimageController.php
The save() action performs an ACL check using edit permission instead of edit.own. The edit permission is granted to all Editor-role users globally, so the check succeeds for any image ID regardless of who created it.
USERIMAGECONTROLLER.PHP — VULNERABLE CODE (LINE 145)
// Vulnerable
if (!$this->checkACL('edit', 'image', $recordId, $parent_id, true)) { ... }
Because core.edit is held globally by the Editor group, the condition evaluates to false for every image ID, granting unrestricted write access. On a successful save, the model additionally updates created_by to the attacker's user ID, silently transferring ownership of the image to the attacker.
File: components/com_joomgallery/tmpl/image/default.php
The frontend image template echoes $this->item->title without HTML-encoding it into the alt attribute context. Joomla's JInput STRING filter does not strip double-quote characters, so a payload containing " breaks out of the attribute and injects arbitrary event handlers.
DEFAULT.PHP — VULNERABLE CODE (LINES 64, 78)
// Vulnerable
item->title; ?>" ...>
The payload abc" onmouseover="alert(document.domain);" x=" is stored in jos_joomgallery.title and injected raw into the HTML attribute on every page render. No sanitization occurs at the storage or display layer.
task=userimage.save&id=3 with XSS payload in jform[title]. ACL check passes (core.edit, not edit.own). Server returns HTTP 303 — not 403.created_by transferred to attacker's user ID.jos_joomgallery.title.alt="abc" onmouseover="alert(document.domain);". XSS fires. Admin session captured → full site compromise.Admin creates admin_image via JoomGallery backend (Joomla 5.4.7). Image is Published, Approved, and owned by Administrator

GET /index.php/component/users/login — response JSON contains "csrf.token":"a68c2b3a...". Token captured for the subsequent login POST.

joomla_user_state=logged_inPOST /index.php/component/users/login with CSRF token and Editor credentials. Response: HTTP 303 and Set-Cookie: joomla_user_state=logged_in. Session cookie captured.

GET /index.php?option=com_joomgallery with session cookie. Response contains a new "csrf.token":"2d96934b..." for use in the save request.

Editor POSTs to option=com_joomgallery&task=userimage.save&id=3 with jform[title] set to:
abc" onmouseover="alert(document.domain);" x="
Server returns HTTP 303 (not 403), confirming the IDOR. Location header shows the XSS payload in the redirect URL, confirming the title was accepted and saved.

JoomGallery backend shows image ID=3 now has Owner: Editor User. The created_by field was silently updated in the database during the unauthorized save.

SQL query on jos_joomgallery confirms the XSS payload is stored — " is stored as a raw double-quote, not as ". No sanitization occurred at the storage layer.

Any user visiting /index.php/component/joomgallery/gallery triggers the payload. The browser's alert() dialog confirms JavaScript execution in the victim's origin (document.domain).

created_by to the attacker, permanently altering the audit trail.alt attribute can exfiltrate the administrator's session cookie, granting the attacker full backend access and control over the entire Joomla installation.S:C), crossing the trust boundary between the attacker's low-privilege session and the victim's high-privilege session.