# CVE-2026-48908 — Variant Patch Analysis

## Changed files in the vendor fix (6.6.2)

A recursive diff between the vulnerable 6.6.1 package and the fixed 6.6.2 package shows that the only source-code change is in the site front-end controller:

| File | 6.6.1 | 6.6.2 |
|------|-------|-------|
| `site/controllers/asset.php` | No authentication/CSRF guard; `loadCustomIcons`, `deleteCustomIcon`, `changeCustomIconStatus`, and `uploadCustomIcon` all reachable from the public task dispatcher | New `__construct()` checks `core.admin`/`core.manage` authorization, a logged-in user, and a valid CSRF token; `unpack()` changed from `public static` to `private static`; the three non-upload custom-icon methods removed from the site controller |
| `language/site/en-GB/en-GB.com_sppagebuilder.ini` | - | New error strings used by the new guard |
| `admin/sql/updates/mysql/6.6.2.sql` | - | New schema update file |
| `sppagebuilder.xml`, `mod_sppagebuilder/mod_sppagebuilder.xml`, `sppagebuilderliteupdater.xml` | 6.6.1 | 6.6.2 version bump |
| `admin/assets/vendor/composer/installed.php` | 6.6.1 | 6.6.2 metadata |

No other controller, helper, or trait was changed.

## What the fix changes at the code level

The vulnerable `SppagebuilderControllerAsset` class inherited from `FormController` and exposed `uploadCustomIcon()` directly to the Joomla task dispatcher. The method:

1. Reads `$_FILES['custom_icon']`.
2. Writes it to the Joomla tmp directory as `builderCustomIcon.zip`.
3. Calls `unpack()` which uses `Joomla\Archive\Archive::extract()`.
4. Copies the extracted `fonts/` directory and CSS files into the web root at `JPATH_ROOT/media/com_sppagebuilder/assets/iconfont/<fontFamily>/`.
5. Applies only a case-sensitive block-list of PHP-related extensions (lower-case `.php`, `.php3`…`.php8`, `.pht`, `.phtml`, `.phar`).

The 6.6.2 fix adds a constructor that runs before any task method:

```php
public function __construct($config = [])
{
    parent::__construct($config);

    $user = Factory::getUser();
    $authorised = $user->authorise('core.admin', 'com_sppagebuilder')
                   || $user->authorise('core.manage', 'com_sppagebuilder');

    if (!$authorised)
    {
        $response['message'] = Text::_('COM_SPPAGEBUILDER_EDITOR_ADMIN_ACCESS_REQUIRED');
        $this->sendResponse($response, 403, true);
    }

    if (!$user->id)
    {
        $response['message'] = Text::_('COM_SPPAGEBUILDER_EDITOR_LOGIN_SESSION_EXPIRED');
        $this->sendResponse($response, 401, true);
    }

    if (!Session::checkToken())
    {
        $response['message'] = Text::_('COM_SPPAGEBUILDER_EDITOR_SESSION_MISMATCHED');
        $this->sendResponse($response, 403, true);
    }
}
```

In addition, the `unpack()` helper was narrowed from `public static` to `private static`, preventing direct invocation as `task=asset.unpack`, and the three other custom-icon CRUD tasks were removed from the site controller (they still exist inside the admin-side `editor/traits/IconsTrait.php`, which is protected by the admin editor controller's own constructor).

## What the fix assumes

- The **only** unauthenticated path to the upload sink is the front-end task `asset.uploadCustomIcon`. The fix assumes that once this controller is gated, no other controller, view, or addon can reach the same sink without already having the same admin/manage privilege.
- The site `asset` controller is **only** used by authenticated administrators, so applying a strict admin/manage gate does not break legitimate front-end use.
- Joomla's `Session::checkToken()` is a sufficient CSRF defense for admin actions.
- The extension block-list inside `uploadCustomIcon()` is acceptable for the *authenticated* use case; the fix does not replace it with a strict allow-list.

## What the fix does NOT cover / remaining gaps

- The upload logic itself is **unchanged**: the ZIP is still extracted and copied into the web root under `/media/com_sppagebuilder/assets/iconfont/`. The file-type filtering is still the same case-sensitive block-list. If an attacker ever obtains a valid administrator session and a CSRF token, the same RCE chain is still possible.
- The fix does **not** add server-side allow-list validation of archive contents, nor does it disable PHP execution / `.htaccess` override in the iconfont directory. Defense in depth (e.g., `AllowOverride None` on `/media/com_sppagebuilder/assets/iconfont/`) still relies on the site administrator's web-server configuration.
- The fix removes the three non-upload CRUD methods from the site controller, but that is not a gap; it is part of the same surface-reduction change.
- No other code path was modified. Controllers that already had authorization checks (`media.php`, `ai_content.php`, `site/views/ajax/view.json.php`, `site/views/systemeditor/view.html.php`, etc.) remain unchanged, because they were already gated.

## Comparison of behavior before and after the fix

| Scenario | 6.6.1 (vulnerable) | 6.6.2 (fixed) |
|----------|---------------------|---------------|
| Unauthenticated `POST index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon` with a malicious ZIP | ZIP accepted, extracted to web root, RCE possible | `403 You require admin access` before the upload runs |
| Unauthenticated `GET` or missing CSRF token to the same task | Reaches upload in 6.6.1 | Blocked by the constructor |
| Direct `task=asset.unpack` with a path | `public static` method is callable, but requires a local path argument | Method is now `private` and the constructor blocks unauthenticated requests first |
| Other asset methods (`loadCustomIcons`, `deleteCustomIcon`, `changeCustomIconStatus`) | Reachable without auth | Removed from the site controller; only available through the admin editor, which has its own auth guard |

## Target threat model / security policy

No `SECURITY.md` file or explicit threat-model document was found inside the SP Page Builder package. The public vendor forum and the Joomla! Project CNA advisory treat the issue as an **unauthenticated** remote code-execution vulnerability: an attacker with no credentials must not be able to upload and execute code. The 6.6.2 fix is scoped to that exact trust boundary. Any bypass claim would have to demonstrate an unauthenticated path that still reaches the upload/extraction sink or a functionally equivalent sink on the patched build.

## Variant search conclusion

The fix is **complete for the claimed unauthenticated vector**. The only meaningful unauthenticated file-upload/extraction sink in the package is the site `asset.uploadCustomIcon` task, and the constructor guard covers it. The admin-side `IconsTrait` is protected by the admin editor's constructor. No other unauthenticated controller or addon performs file writes to the web root. The variant stage therefore focuses on ruling out bypasses around the constructor (GET requests, alternate task names, direct `unpack`, and the admin trait endpoint) rather than finding a new sink.

