CVE-2026-48908: Verified Repro With Script Download
CVE-2026-48908: SP Page Builder for Joomla allows unauthenticated arbitrary file upload via asset.uploadCustomIcon, enabling PHP upload and remote code execution.
CVE-2026-48908 is verified against SP Page Builder (com_sppagebuilder) · Joomla extension. Affected versions: 1.0.0 through 6.6.1. Fixed in 6.6.2. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00269.
What Is CVE-2026-48908?
CVE-2026-48908 is a critical unauthenticated arbitrary file upload vulnerability in SP Page Builder (com_sppagebuilder) for Joomla, reachable via the asset.uploadCustomIcon task, that lets an unauthenticated attacker upload a web shell and achieve remote code execution. Pruva reproduced it (reproduction REPRO-2026-00269).
CVE-2026-48908 Severity & CVSS Score
CVE-2026-48908 is rated critical severity, with a CVSS base score of 10.0 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected SP Page Builder (com_sppagebuilder) Versions
SP Page Builder (com_sppagebuilder) · Joomla extension versions 1.0.0 through 6.6.1 are affected.
How to Reproduce CVE-2026-48908
pruva-verify REPRO-2026-00269 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00269/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-48908
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
multipart ZIP archive (custom_icon) containing .htaccess + uppercase .PHP web shell
- index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon
reproduction_steps.sh No bypass or distinct RCE variant found for CVE-2026-48908. The 6.6.2 auth/CSRF guard on the site SppagebuilderControllerAsset blocks all tested alternate entry points (missing controller prefix, direct unpack, admin editor.icons, and the read-only loadCustomIcons method).
How the agent worked
Root Cause and Exploit Chain for CVE-2026-48908
SP Page Builder for Joomla (com_sppagebuilder) exposes the controller task
index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon to upload a
custom icon-font package. In versions 1.0.0 – 6.6.1 the task is reachable
without authentication, authorization, or a valid anti-CSRF token. The uploaded
ZIP archive is extracted and copied into the web root under
/media/com_sppagebuilder/assets/iconfont/<name>/, including its fonts/
subdirectory. Because the archive entries are not validated against an
extension allow-list, an attacker can package a web shell and a .htaccess
file that registers an uppercase .PHP extension with the PHP handler, then
execute the shell over HTTP. This yields unauthenticated remote code execution
as the web-server user.
- Package / component: SP Page Builder (
com_sppagebuilder) by JoomShaper —site/controllers/asset.php(SppagebuilderControllerAsset::uploadCustomIcon). - Affected versions: 1.0.0 – 6.6.1.
- Patched version: 6.6.2.
- Risk level: Critical (CWE-284 → CWE-434 Unrestricted Upload of File with Dangerous Type; advisory CVSS 4.0 10.0). Any unauthenticated remote attacker can upload and execute attacker-controlled PHP code on the Joomla host.
Impact Parity
- Disclosed / claimed maximum impact: Unauthenticated arbitrary file upload leading to remote code execution.
- Reproduced impact from this run: Full parity.
- Unauthenticated upload: The PoC sent a multipart
custom_iconZIP to theasset.uploadCustomIcontask with no Joomla session cookie and no CSRF token; the server accepted it and extracted the contents under the web root. - Code execution: The uploaded
.htaccessplus uppercase.PHPshell executed the PoC’secho 7*6marker (SPPB-RCE-42) and thenid, returninguid=33(www-data) gid=33(www-data) groups=33(www-data). - Negative control: The same PoC against the fixed 6.6.2 install received
HTTP 403 with the message
You require admin access, confirming the patch closes the path.
- Unauthenticated upload: The PoC sent a multipart
- Parity:
full. - Not demonstrated: Further post-exploitation (e.g., privilege escalation
beyond
www-data) is not part of the CVE claim.
Root Cause
In the vulnerable build the SppagebuilderControllerAsset class (in
site/controllers/asset.php) has no authentication, authorization, or CSRF
logic in its constructor or in the uploadCustomIcon() method. The method
reads the uploaded custom_icon file, validates only basic size/length limits,
writes it to a temporary ZIP, and calls the local unpack() method:
$zip_file = $tmp_path . '/builderCustomIcon.zip';
...
if (File::upload($tmp_src, $zip_file, false, true))
{
$extract = $this->unpack($zip_file);
...
Folder::copy($extract_path . '/' . $font_path, $rootPath . '/' . $fontFamily . '/fonts');
File::copy($extract_path . '/' . $font_css, $rootPath . '/' . $fontFamily . '/' . $font_css);
...
}
unpack() uses Joomla\Archive\Archive::extract() to unpack the ZIP into a
subdirectory of the Joomla tmp_path. The controller then copies the entire
fonts/ (and CSS) contents into the public web-root directory
JPATH_ROOT/media/com_sppagebuilder/assets/iconfont/<fontFamily>/. No
extension allow-list is enforced on the archive entries before copying, so the
attacker can include arbitrary files such as .htaccess and .PHP.
The server-side extension filter that does exist is a case-sensitive
block-list (rejects lowercase .php, .php3–.php8, .pht, .phtml,
.phar, etc.), but the block-list misses uppercase .PHP and .htaccess.
A default Apache PHP handler (<FilesMatch "\.php$">) does not execute
uppercase .PHP, so the PoC also drops a fonts/.htaccess containing:
AddType application/x-httpd-php .PHP
Where AllowOverride is permitted, this reconfigures the directory to treat
.PHP as PHP. The same HTTP request that uploaded the shell can then fetch it
from the web-accessible iconfont directory and execute it.
The 6.6.2 fix adds a constructor to SppagebuilderControllerAsset that
blocks unauthenticated/unauthorized requests before any method runs:
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)
{
...
$this->sendResponse($response, 403, true);
}
if (!$user->id)
{
...
$this->sendResponse($response, 401, true);
}
if (!Session::checkToken())
{
...
$this->sendResponse($response, 403, true);
}
}
This requires the user to be logged in, to have admin/manage privileges for the component, and to present a valid Joomla CSRF token, making the upload task unreachable from an unauthenticated attacker.
Reproduction Steps
- Script:
bundle/repro/reproduction_steps.sh(self-contained; exits 0 on confirmation, non-zero on failure). - What it does:
- Reads the durable project cache if one is configured, otherwise creates
bundle/artifacts/sppb. - Clones the public PoC
https://github.com/papageo75/CVE-2026-48908-PoC.gitonce and reuses it across runs. - For each role (
vulnandfixed):- Builds a Docker Compose stack with
joomla:5.2-php8.2-apacheandmysql:8.0, downloads the SP Page Builder ZIP (6.6.1for the vulnerable role,6.6.2for the fixed role), and installs it through Joomla’s web installer using the real administrator workflow. - Waits for the Joomla health check to return HTTP 200.
- Runs the PoC against the running container from the same Docker network
(
http://joomla), using the realasset.uploadCustomIconHTTP endpoint.
- Builds a Docker Compose stack with
- Writes
bundle/repro/runtime_manifest.jsonand per-role evidence logs underbundle/logs/.
- Reads the durable project cache if one is configured, otherwise creates
- Expected evidence of reproduction:
- Vulnerable (6.6.1): PoC prints
CODE EXECUTION CONFIRMED via '.htaccess+.PHP'and the output ofid(uid=33(www-data)). - Fixed (6.6.2): PoC prints
TARGET NOT VULNERABLE — SP Page Builder is patchedand the first upload attempt returns403 'require admin' = patched.
- Vulnerable (6.6.1): PoC prints
Evidence
Log file locations (written by reproduction_steps.sh):
bundle/logs/vuln_compose.log— Docker stack creation for the vulnerable run.bundle/logs/vuln_poc.log— PoC transcript showing RCE on 6.6.1.bundle/logs/fixed_compose.log— Docker stack creation for the fixed run.bundle/logs/fixed_poc.log— PoC transcript showing 6.6.2 rejects the upload.bundle/logs/reproduction_steps.log— full transcript of the latest run.bundle/repro/runtime_manifest.json— structured runtime evidence.
Key excerpts (from the latest verification run):
Vulnerable 6.6.1 — code execution confirmed:
[*] try .htaccess+.PHP -> EXECUTED
[+] CODE EXECUTION CONFIRMED via '.htaccess+.PHP' (echo 7*6 -> 42)
[+] webshell : http://joomla/media/com_sppagebuilder/assets/iconfont/.../fonts/....PHP?t=...&c=<cmd>
[*] running: id
------------------------------------------------------------
uid=33(www-data) gid=33(www-data) groups=33(www-data)
------------------------------------------------------------
Fixed 6.6.2 — upload now requires admin authentication:
[*] try .php -> 403 'require admin' = patched
[-] TARGET NOT VULNERABLE — SP Page Builder is patched (6.6.2+); the upload task now requires authenticated admin access.
Environment: Joomla 5.2 with PHP 8.2 (Apache), MySQL 8.0, SP Page Builder
6.6.1 (vulnerable) and 6.6.2 (fixed), running in Docker on a single custom
network. The PoC is executed from an ephemeral python:3-slim container on the
same network, with no session state or credentials.
Recommendations / Next Steps
- Upgrade to SP Page Builder 6.6.2 or later. The 6.6.2 patch adds the constructor checks described above, which are the primary fix.
- Defence in depth on the web server:
- Disable PHP execution in directories that are only expected to host static
assets (
/media/,/images/,/templates/*/css, etc.). - Set
AllowOverride Noneon these directories so an uploaded.htaccesscannot re-register handlers. - Use a case-insensitive extension allow-list (e.g., only
.ttf,.woff,.css,.json) on uploaded archive contents; never rely on a block-list of dangerous extensions.
- Disable PHP execution in directories that are only expected to host static
assets (
- Assume-breach review: search for unexpected
.php/.PHP,.htaccess, and newly created directories under/media/com_sppagebuilder/assets/iconfont/, and look for new Super User accounts or planted web shells. - Backport: if a site cannot upgrade immediately, apply the 6.6.2
constructor guard (authorization + user session +
Session::checkToken()) tosite/controllers/asset.php.
Additional Notes
- Idempotency: The reproduction script was run twice consecutively; both runs exited 0 with the same positive RCE evidence on the vulnerable build and the same 403 rejection on the fixed build.
- Limitations: The full RCE chain depends on the Apache configuration
permitting
AllowOverridein the iconfont directory and on PHP being enabled there. IfAllowOverrideis disabled or the case-sensitive.PHPhandler is blocked, the bug still permits unauthenticated file write but not direct RCE; this is still an unauthenticated arbitrary file upload vulnerability. - References:
- NVD — https://nvd.nist.gov/vuln/detail/CVE-2026-48908
- Technical write-up — https://mysites.guru/blog/sp-page-builder-zero-day-uploadcustomicon-rce/
- PoC repository — https://github.com/papageo75/CVE-2026-48908-PoC
- Vendor — https://www.joomshaper.com/page-builder
Variant Analysis & Alternative Triggers for CVE-2026-48908
This variant stage tested whether SP Page Builder 6.6.2 can be bypassed or whether the same underlying unauthenticated file-upload bug can be triggered from a different entry point. No bypass or distinct RCE variant was found. The vendor fix adds an authorization/CSRF guard to the front-end SppagebuilderControllerAsset class, and that guard blocks every tested unauthenticated path that reaches the same ZIP extraction sink. A sibling unauthenticated method (asset.loadCustomIcons) does exist on the vulnerable build, but it reads from the database rather than writing files, so it is not a variant of the file-upload/RCE vulnerability.
Fix Coverage / Assumptions
What the fix covers
- The only unauthenticated path to the ZIP upload/extraction sink is the front-end task
index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon. - The 6.6.2 patch adds a constructor to
site/controllers/asset.phpthat checks:core.adminorcore.manageauthorization for the component.- A logged-in user (
$user->id). - A valid Joomla CSRF token via
Session::checkToken().
- The patch also removes the three non-upload CRUD methods (
loadCustomIcons,deleteCustomIcon,changeCustomIconStatus) from the site controller and narrowsunpack()frompublic statictoprivate static, preventing direct task invocation.
What the fix does NOT cover
- The upload logic itself is unchanged: the ZIP is still extracted and copied into the web root under
/media/com_sppagebuilder/assets/iconfont/. The extension filter is still a case-sensitive block-list. An attacker with a valid administrator session and a CSRF token could still execute the same RCE chain. - The fix does not add a content allow-list, restrict the iconfont directory to static assets, or disable
.htaccess/ PHP execution in that directory. Those remain defense-in-depth gaps that the site administrator must address on the web server. - No code paths outside
site/controllers/asset.phpwere modified, because every other file-upload or file-write controller already had authorization checks (media.php,ai_content.php, the admineditor.php,site/views/ajax/view.json.php, etc.).
Variant / Alternate Trigger
Five candidate entry points were tested against the vulnerable (6.6.1) and fixed (6.6.2) builds:
| Candidate | Vulnerable 6.6.1 | Fixed 6.6.2 | Assessment |
|---|---|---|---|
asset.uploadCustomIcon (control) |
RCE confirmed | 403 "require admin" |
The fix covers the original sink. |
uploadCustomIcon without controller prefix |
404 |
404 |
Joomla routes to the default controller, not the asset controller. Not a bypass. |
asset.unpack (direct) |
500 (missing argument) |
403 |
The method was never reachable with attacker-controlled input; the fix blocks it anyway. |
editor.icons via /administrator/ |
Blocked by auth | Blocked by auth | The admin IconsTrait is protected by the admin editor's own constructor. |
asset.loadCustomIcons |
200 (unauthenticated list) |
403 |
Same controller, but the method only reads from the database; it does not upload or extract files, so it is not a variant of the file-upload/RCE bug. |
Code paths involved
site/controllers/asset.php—SppagebuilderControllerAsset::__construct()(new guard) anduploadCustomIcon()(the sink).admin/editor/traits/IconsTrait.php—uploadIcons()(same logic, but only used by the admin editor, which is protected byadmin/controllers/editor.php).admin/controllers/editor.php—SppagebuilderControllerEditor::__construct()(admin-side guard).Package / component: SP Page Builder (
com_sppagebuilder) by JoomShaper.Affected versions (as tested): 6.6.1 vulnerable, 6.6.2 fixed.
Risk level: No new risk introduced by this variant search. The original unauthenticated RCE is fully mitigated by the 6.6.2 guard for the tested paths.
Impact Parity
- Disclosed/claimed maximum impact: Unauthenticated arbitrary file upload leading to remote code execution.
- Reproduced impact from this variant run: None — no distinct bypass or alternate RCE trigger was confirmed.
- Parity:
nonefor the variant stage. The original RCE was reproduced as a control on 6.6.1. - Not demonstrated: Further post-exploitation or privilege escalation beyond the reproduced
www-datashell.
Root Cause
The root cause of the original CVE is missing authentication/authorization on the SppagebuilderControllerAsset class, which allowed any remote caller to reach uploadCustomIcon() and extract an attacker-controlled ZIP under the web root. The 6.6.2 fix removes that missing-auth condition by adding a constructor guard. Because the guard runs for every task of that class, no alternative task name or direct method invocation can bypass it from an unauthenticated session. The admin-side IconsTrait shares the same extraction logic but is already protected by the admin editor's constructor, so it cannot be used as an unauthenticated variant either.
Reproduction Steps
- Script:
bundle/vuln_variant/reproduction_steps.sh(self-contained, idempotent; exits 1 when no bypass is found). - What it does:
- Creates two Docker Compose stacks (
joomla:5.2-php8.2-apache+mysql:8.0) for 6.6.1 and 6.6.2. - Installs the corresponding SP Page Builder ZIP through the real Joomla web installer.
- Runs a Python variant tester that uploads the same
.htaccess + .PHPpayload to each candidate endpoint and tries to execute the dropped shell. - Runs the original public PoC as a control for each build.
- Creates two Docker Compose stacks (
- Expected evidence:
- Vulnerable 6.6.1: control endpoint prints
RCEand the PoC printsCODE EXECUTION CONFIRMED. - Fixed 6.6.2: every candidate endpoint returns an auth-related block (403/"require admin") and the PoC prints
TARGET NOT VULNERABLE.
- Vulnerable 6.6.1: control endpoint prints
Evidence
Log files produced by the variant script (both runs produced the same result):
bundle/logs/variant_vuln_test.log— variant matrix on the vulnerable build.bundle/logs/variant_fixed_test.log— variant matrix on the fixed build.bundle/logs/variant_vuln_poc_control.log— original PoC confirming RCE on 6.6.1.bundle/logs/variant_fixed_poc_control.log— original PoC confirming 6.6.2 rejects the upload.bundle/logs/variant_vuln_compose.logandbundle/logs/variant_fixed_compose.log— Docker stack creation.bundle/vuln_variant/runtime_manifest.json— structured runtime evidence.
Key excerpts from the latest run:
Vulnerable 6.6.1 — control endpoint RCE:
{
"control_asset.uploadCustomIcon": {
"result": "RCE",
"detail": "SPPB-VAR-42\n",
"iconfont_dir": "media/com_sppagebuilder/assets/iconfont/icovoqhdk"
}
}
Fixed 6.6.2 — all candidates blocked:
{
"control_asset.uploadCustomIcon": { "result": "BLOCKED", "detail": "auth/csrf message", "status": 403 },
"variant_task_no_prefix": { "result": "BLOCKED", "detail": "non-JSON response", "status": 404 },
"variant_direct_unpack": { "result": "BLOCKED", "detail": "auth/csrf message", "status": 403 },
"variant_admin_editor.icons": { "result": "BLOCKED", "detail": "auth/csrf message", "status": 200 },
"variant_asset.loadCustomIcons": { "result": "BLOCKED", "detail": "auth/csrf message", "status": 403 }
}
Original PoC control on 6.6.2:
[*] try .php -> 403 'require admin' = patched
[-] TARGET NOT VULNERABLE — SP Page Builder is patched (6.6.2+)
Environment: Joomla 5.2 + PHP 8.2 (Apache) + MySQL 8.0, SP Page Builder 6.6.1 and 6.6.2 in Docker, tested from an ephemeral python:3-slim container on the same network.
Recommendations / Next Steps
- No additional code change is required for the unauthenticated vector. The 6.6.2 fix is complete for the claimed scope.
- Defense in depth:
- Add a strict extension allow-list to the icon-font upload (only
.ttf,.woff,.woff2,.css,.json) instead of relying on the case-sensitive block-list. - Disable PHP execution and
AllowOverrideunder/media/com_sppagebuilder/assets/iconfont/at the web-server level.
- Add a strict extension allow-list to the icon-font upload (only
- Monitor for partial updates: A forum post suggested some 6.6.2 installations might still have an unpatched
site/controllers/asset.phpif the extension update was incomplete. Verify the patched file hash after upgrading. - Assume-breach review: As recommended in the original repro, check for unexpected
.php/.PHPfiles,.htaccess, and newly created directories under the iconfont path, and look for rogue Super User accounts.
Additional Notes
- Idempotency: The variant script was run twice consecutively. Both runs exited 1 with the same results: the vulnerable build reproduced the original RCE, the fixed build blocked all tested paths, and no bypass/variant was found.
- Limitations: The variant search focused on entry points and task names within the SP Page Builder component. It did not attempt to bypass a valid Joomla admin session or CSRF token; such a scenario would be an authenticated compromise, not a bypass of the unauthenticated vulnerability.
- Threat model: No
SECURITY.mdor explicit threat model was found in the package. The Joomla! Project CNA and vendor advisory scope the issue as unauthenticated remote code execution, and the 6.6.2 fix is scoped to that exact trust boundary.
CVE-2026-48908 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-48908
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-48908
Upgrade SP Page Builder (com_sppagebuilder) · Joomla extension to 6.6.2 or later.
FAQ: CVE-2026-48908
How does the CVE-2026-48908 upload-to-RCE attack work?
.htaccess file that registers an uppercase .PHP extension with the PHP handler (evading naive extension filters) to the unauthenticated uploadCustomIcon task; the archive is extracted under the web root and the shell is then invoked directly over HTTP to execute arbitrary PHP.Which SP Page Builder versions are affected by CVE-2026-48908, and where is it fixed?
How severe is CVE-2026-48908?
How can I reproduce CVE-2026-48908?
asset.uploadCustomIcon task and demonstrates the extracted PHP shell executing under the web server user.References for CVE-2026-48908
Authoritative sources for CVE-2026-48908 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.