# REPRO-2026-00269: SP Page Builder for Joomla allows unauthenticated arbitrary file upload via asset.uploadCustomIcon, enabling PHP upload and remote code execution. ## Summary Status: published Severity: critical Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00269 CVE: CVE-2026-48908 ## Package Name: SP Page Builder (com_sppagebuilder) Ecosystem: Joomla extension Affected: 1.0.0 through 6.6.1 Fixed: 6.6.2 ## Root Cause # CVE-2026-48908 — Root Cause Analysis ## Summary 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//`, 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. ## Impact - **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. 1. **Unauthenticated upload:** The PoC sent a multipart `custom_icon` ZIP to the `asset.uploadCustomIcon` task with no Joomla session cookie and no CSRF token; the server accepted it and extracted the contents under the web root. 2. **Code execution:** The uploaded `.htaccess` plus uppercase `.PHP` shell executed the PoC’s `echo 7*6` marker (`SPPB-RCE-42`) and then `id`, returning `uid=33(www-data) gid=33(www-data) groups=33(www-data)`. 3. **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. - **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: ```php $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//`. 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 (``) does not execute uppercase `.PHP`, so the PoC also drops a `fonts/.htaccess` containing: ```apache 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: ```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) { ... $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 1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained; exits 0 on confirmation, non-zero on failure). 2. **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.git` once and reuses it across runs. - For each role (`vuln` and `fixed`): - Builds a Docker Compose stack with `joomla:5.2-php8.2-apache` and `mysql:8.0`, downloads the SP Page Builder ZIP (`6.6.1` for the vulnerable role, `6.6.2` for 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 real `asset.uploadCustomIcon` HTTP endpoint. - Writes `bundle/repro/runtime_manifest.json` and per-role evidence logs under `bundle/logs/`. 3. **Expected evidence of reproduction:** - Vulnerable (6.6.1): PoC prints `CODE EXECUTION CONFIRMED via '.htaccess+.PHP'` and the output of `id` (`uid=33(www-data)`). - Fixed (6.6.2): PoC prints `TARGET NOT VULNERABLE — SP Page Builder is patched` and the first upload attempt returns `403 'require admin' = patched`. ## 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= [*] 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 None` on these directories so an uploaded `.htaccess` cannot 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. - **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()`) to `site/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 `AllowOverride` in the iconfont directory and on PHP being enabled there. If `AllowOverride` is disabled or the case-sensitive `.PHP` handler 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 ## Reproduction Details Reproduced: 2026-07-08T04:49:25.740Z Duration: 4671 seconds Tool calls: 489 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00269 pruva-verify CVE-2026-48908 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00269&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00269/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48908 - Source: https://nvd.nist.gov/vuln/detail/CVE-2026-48908, https://mysites.guru/blog/sp-page-builder-zero-day-uploadcustomicon-rce/, https://github.com/papageo75/CVE-2026-48908-PoC, https://www.joomshaper.com/page-builder ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 9057 bytes) - bundle/repro/rca_report.md (analysis, 9983 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 14783 bytes) - bundle/vuln_variant/rca_report.md (analysis, 9544 bytes) - bundle/artifact_promotion_manifest.json (other, 7373 bytes) - bundle/artifact_promotion_report.json (other, 10374 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1479 bytes) - bundle/repro/validation_verdict.json (other, 725 bytes) - bundle/repro/runtime_manifest.json (other, 545 bytes) - bundle/logs/reproduction_steps.log (log, 6283 bytes) - bundle/logs/vuln_poc.log (log, 1667 bytes) - bundle/logs/fixed_poc.log (log, 652 bytes) - bundle/logs/vuln_compose.log (log, 1072 bytes) - bundle/logs/fixed_compose.log (log, 1097 bytes) - bundle/logs/variant_vuln_test.log (log, 1661 bytes) - bundle/logs/variant_fixed_test.log (log, 1172 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3082 bytes) - bundle/vuln_variant/validation_verdict.json (other, 980 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 6992 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 682 bytes) - bundle/logs/variant_vuln_poc_control.log (log, 1667 bytes) - bundle/logs/variant_fixed_poc_control.log (log, 652 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00269 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00269/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00269 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev