CVE-2026-56290: Verified Repro With Script Download
CVE-2026-56290: Page Builder CK unauthenticated arbitrary file upload to RCE
CVE-2026-56290 is verified against the affected target. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00232.
What Is CVE-2026-56290?
CVE-2026-56290 is a critical unauthenticated arbitrary file upload vulnerability in the Page Builder CK Joomla extension (com_pagebuilderck) that lets any remote visitor upload and execute a PHP web shell. Pruva reproduced it (reproduction REPRO-2026-00232).
CVE-2026-56290 Severity & CVSS Score
CVE-2026-56290 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
How to Reproduce CVE-2026-56290
pruva-verify REPRO-2026-00232 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00232/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-56290
- 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
unauthenticated HTTP POST uploading a PHP web shell via the com_pagebuilderck browse.ajaxAddPicture endpoint with attacker-controlled path and filename
- GET public page
- extract CSRF token
- POST file upload to index.php?option=com_pagebuilderck&task=browse.ajaxAddPicture&<token>=1
- access uploaded .php file via HTTP
Survey of alternate triggers/bypasses for the Page Builder CK unauthenticated arbitrary file upload to RCE. No bypass of the v3.6.0/3.6.1 fix found: the central CKController::execute() authorization check covers all non-display controller tasks, and ajaxAddPicture is the only sink accepting attacker-controlled file ex…
How the agent worked
Root Cause and Exploit Chain for CVE-2026-56290
CVE-2026-56290 is a critical unauthenticated arbitrary file upload vulnerability in the Page Builder CK Joomla extension (com_pagebuilderck) by Cédric Keiflin (joomlack.fr). The extension's front-end file upload handler (browse.ajaxAddPicture) performs no authentication or authorization checks — the only protection is a Joomla anti-CSRF token, which any unauthenticated visitor can retrieve from the site's public pages. Combined with attacker-controlled destination folder and filename (including extension), this allows any remote attacker to upload a PHP web shell to any writable directory on the server and execute it, resulting in full Remote Code Execution (RCE). The vulnerability affects all versions up to and including 3.5.10, and was fixed in version 3.6.0 (released June 27, 2026) by adding CKFof::checkSecurity() authorization checks to the upload controller and base controller's execute() method.
- Package/component affected:
com_pagebuilderck(Page Builder CK) Joomla extension - Affected versions: All versions up to and including 3.5.10 (current line); also back-ported fixes for Joomla 3 (3.1.1) and Joomla 4 (3.4.10)
- Risk level: Critical (CVSS 4.0 Base Score: 10.0)
- Consequences: Full Remote Code Execution on the web server. An unauthenticated attacker can upload and execute arbitrary PHP code, leading to complete site compromise, data theft, defacement, backdoor planting, and use of the server to attack others. Active in-the-wild exploitation confirmed: web shells (e.g.,
bhup.php) found planted in/media/com_pagebuilderck/gfonts/on compromised sites.
Impact Parity
- Disclosed/claimed maximum impact: Unauthenticated arbitrary file upload leading to full Remote Code Execution (CVSS 10.0, CWE-284/CWE-434)
- Reproduced impact from this run: Full RCE confirmed — an unauthenticated attacker uploaded a PHP web shell to the server and executed it via HTTP, obtaining server OS information (uname, current user) as proof of code execution
- Parity:
full - Not demonstrated: N/A — the complete attack chain from unauthenticated request to code execution was demonstrated end-to-end
Root Cause
The vulnerability exists in the CKBrowse::ajaxAddPicture() method, which handles file uploads for the Page Builder CK extension's media manager. The root cause is a failure of access control (CWE-284):
No authentication check: The upload endpoint is accessible via the front-end (
site/controllers/browse.phpincludes the admin controller). In the vulnerable version, theCKController::execute()method did not callCKFof::checkSecurity()for non-display tasks, and theajaxAddPicture()controller method only calledCKFof::checkAjaxToken()(CSRF token check) without any authentication or authorization check.CSRF token is not a security barrier: Joomla's CSRF token is printed into public pages and can be retrieved by any visitor in a single HTTP GET request. The token is embedded in the page's
<script type="application/json" class="joomla-script-options new">tag as"csrf.token":"<32-char-hex>".Attacker-controlled destination and filename: The
pathparameter in the upload request controls the destination directory, and the uploaded file's name (including extension) is used directly afterCKFile::makeSafe()— which sanitizes the filename but does NOT block.phpextensions. The extension check code is commented out in the source.No file type validation: The upload handler accepts any file type. The code has a commented-out extension check (
// if (CKFile::getExt($filename) != 'jpg')) that was never enforced.
The fix (version 3.6.0/3.6.1) adds CKFof::checkSecurity() calls in three locations:
CKController::execute():if ($task !== 'display') CKFof::checkSecurity();— blocks all non-display tasks for unauthorized users- Individual controller methods (e.g.,
browse.ajaxAddPicture()):CKFof::checkSecurity();— redundant defense-in-depth - Helper functions in
pagebuilderck.php
CKFof::checkSecurity() calls $user->authorise('core.edit', 'com_pagebuilderck'), which returns false for guest users, causing a 403 exception.
Reproduction Steps
- Reference:
bundle/repro/reproduction_steps.sh - What the script does:
- Starts Joomla 5.2 + MariaDB 10.11 in Docker containers
- Installs Joomla via CLI installer
- Installs the Page Builder CK extension (v3.6.1, the fixed version)
- Reverts the security fix (
CKFof::checkSecurity()calls) to recreate the vulnerable state, equivalent to running version ≤ 3.5.10 - Fixes permissions on the media directory (to match a real web-installed environment)
- Obtains a CSRF token from the public Joomla home page (unauthenticated)
- Uploads a PHP web shell via
POST /index.php?option=com_pagebuilderck&task=browse.ajaxAddPicture&<token>=1withpath=media/com_pagebuilderck/gfonts - Accesses the uploaded web shell via HTTP to execute PHP code (RCE proof)
- Restores the fixed version and tests the negative control (should return 403)
- Expected evidence of reproduction:
- Upload response:
{"img":"/media/com_pagebuilderck/gfonts/exploit_shell.php","filename":"exploit_shell.php"} - RCE output:
RCE_PROOF_<timestamp>Linux <hostname> <kernel> x86_64_www-data - Fixed version: HTTP 403 "You don't have permission to access this"
- Upload response:
Evidence
Log file locations
bundle/logs/evidence.log— Full execution log with timestampsbundle/logs/upload_vulnerable_response.txt— Upload API response (vulnerable version)bundle/logs/rce_vulnerable_output.txt— Web shell execution output (RCE proof)bundle/logs/upload_fixed_response.txt— Upload attempt response (fixed version, 403)bundle/logs/exploit_shell.php— The uploaded PHP web shellbundle/repro/runtime_manifest.json— Runtime evidence manifest
Key excerpts proving reproduction
Upload success (vulnerable version):
{"img" : "/media/com_pagebuilderck/gfonts/exploit_shell.php", "filename" : "exploit_shell.php"}
RCE proof (web shell execution):
RCE_PROOF_1783247923Linux a32046ad96c2 7.0.14-arch1-1 #1 SMP PREEMPT_DYNAMIC Sat, 27 Jun 2026 16:15:10 +0000 x86_64_www-data
Negative control (fixed version):
<title>Error: 403</title>
...
<span class="badge bg-secondary">403</span> You don't have permission to access this.
Environment details
- Joomla 5.2 (PHP 8.2.28, Apache 2.4.62)
- MariaDB 10.11
- Page Builder CK 3.6.1 (fix reverted to simulate ≤3.5.10)
- Docker containers with isolated network
Recommendations / Next Steps
- Immediate action: Upgrade Page Builder CK to version 3.6.0+ (or 3.1.1 for Joomla 3, 3.4.10 for Joomla 4)
- Check for compromise: Search for stray
.phpfiles under/media/com_pagebuilderck/and its subfolders (especiallygfonts/), and more broadly for PHP files containing upload handler signatures like$_POST['_upl'] - WAF rule: Block unauthenticated POST requests to
index.php?option=com_pagebuilderck&task=browse.ajaxAddPictureas a temporary mitigation - Server hardening: Disable PHP execution in upload/media directories via
.htaccess(note: this is not a complete fix since the attacker controls the destination folder) - Joomla core: Keep Joomla updated to benefit from core
com_ajaxACL hardening (Joomla 5.4.4+/6.0.4+)
Additional Notes
- Idempotency: The script is idempotent — it tears down existing containers with
docker compose down -vbefore starting fresh ones. Each run creates a clean environment. - Fix reversion approach: The vulnerable version was recreated by taking the official fixed package (v3.6.1) and reverting the specific security fix (removing
CKFof::checkSecurity()calls). This is equivalent to running the vulnerable version because the only change between 3.5.10 and 3.6.0 was the addition of these authorization checks. A minor$_FILESdirect-access patch was also applied for Joomla 5.2 Input class compatibility. - Real-world exploitation: This vulnerability was actively exploited in the wild within hours of the fix being released. Web shells were planted in
/media/com_pagebuilderck/gfonts/bhup.php— the same directory path used in this reproduction. - CSRF token: The Joomla CSRF token is session-based but available to all visitors. Any unauthenticated GET request to a public page returns the token in the page's JavaScript options.
Variant Analysis & Alternative Triggers for CVE-2026-56290
This stage surveyed the Page Builder CK (com_pagebuilderck) extension for a bypass of the v3.6.0/3.6.1 security fix, or an alternate trigger of the same unauthenticated-arbitrary-file-upload-to-RCE root cause. No bypass was found. The fix is comprehensive: a single central authorization check in CKController::execute() (if ($task !== 'display') CKFof::checkSecurity();) guards every non-display controller task for the component, and the only sink that can deliver a .php web shell — CKBrowse::ajaxAddPicture(), whose extension allowlist is commented out — is protected at both the dispatch level and the method level. Empirically, on the fixed v3.6.1 install, every surveyed variant returned 403 (or a non-RCE error) and no .php file was written to the media tree; on the vulnerable state (fix reverted, ≤ 3.5.10) the original browse.ajaxAddPicture endpoint reproduced full RCE, confirming the harness and the fix's coverage. Alternate ajax endpoints (fonts.save, pixabay.upload, browse.ajaxCreateFolder, frontedition.createModule, the ajaxParamsCall gadget) share the broader "missing-authorization" weakness on the vulnerable version but cannot deliver a .php web shell because they force safe extensions (.woff2/.ttf, image extensions, .txt, .css) or perform DB-only writes, and they are all blocked by the fix.
Fix Coverage / Assumptions
- Invariant the fix relies on: every
index.php?option=com_pagebuilderck&task=…request is dispatched throughCKController::execute(). The site entry point (site/pagebuilderck.php) always calls$controller->execute($input->get('task')), and the site controllers merelyinclude_oncethe administrator controllers, so front-end and back-end share the sameexecute(). There is no alternate routing into controller methods. - Code paths explicitly covered: all non-
displaycontroller tasks —browse.ajaxAddPicture,browse.ajaxCreateFolder,browse.getFiles,fonts.save,pixabay.upload,page.save,frontedition.createModule,ajaxParamsCall, and ~15 other ajax methods incontroller.php— via Layer 1 (execute()) plus Layer 2 (per-methodcheckSecurity()). - What the fix does NOT cover: the
displaytask is exempt from the authz check. This is safe becausedisplay()only renders views and writes no.phpfile (verified:task=display&view=browsewrites nothing tomedia/com_pagebuilderck). Out-of-execute()write paths (front-end CSS compilerPagebuilderckFrontHelper::loadAllCss(), thewriteLabelsFile()loader) are not gated byexecute(), but they force.css/.txtextensions and sanitize the path, so they cannot achieve PHP RCE. - Assumption that holds:
Joomla\CMS\User::authorise('core.edit', 'com_pagebuilderck')is false for guests, socheckSecurity()throws 403 for unauthenticated callers. The pre-existing CSRF token gate (checkAjaxToken) is not an authorization boundary (the token is harvestable from public pages) and is correctly supplemented bycheckSecurity().
Variant / Alternate Trigger
No bypass of the fix was found. The following materially distinct entry points / data paths were tested (entry point: unauthenticated HTTP to index.php?option=com_pagebuilderck&task=<controller.task>&<guest_csrf_token>=1):
| ID | Task | Sink (file / function) | Fixed v3.6.1 | Vulnerable |
|---|---|---|---|---|
| C1 | browse.ajaxAddPicture |
helpers/ckbrowse.php CKBrowse::ajaxAddPicture() — arbitrary-extension $_FILES upload (file_put_contents(JPATH_SITE.$imgpath.$filename, $data), allowlist commented at ckbrowse.php:264) |
403, no .php |
200 → RCE |
| C2 | browse.ajaxCreateFolder |
CKBrowse::createFolder() — folder creation |
403 | 200 (reachable) |
| C3 | browse.getFiles |
CKBrowse::getImagesInFolder() — file listing |
403 | reachable |
| C4 | fonts.save (local=1) |
controllers/fonts.php save() — writes font files (forced .woff2/.ttf) |
403 | 403 (extra guard; not RCE) |
| C5 | pixabay.upload |
controllers/pixabay.php upload() — remote fetch+write (img ext allowlist, pixabay-only URL) |
403 | 200 (reachable; not RCE) |
| C6 | frontedition.createModule |
site/controllers/frontedition.php createModule() — DB module insert (no file write; no method-level checkSecurity) |
500 | 500 (reachable; not RCE) |
| C7 | page.save |
site/controllers/page.php save() → model DB write |
403 | reachable |
| C8 | display (view=browse) |
CKController::display() — view rendering (the display exemption) |
403 (view ACL), no .php written |
renders, no .php |
| C9 | ajaxParamsCall |
controller.php ajaxParamsCall() — arbitrary helper-method dispatch gadget (target PagebuilderckLoaderFolder::writeLabelsFile, forced .txt) |
403 | 200 (reachable; .txt only) |
| C10 | browse.ajaxAddPicture via /administrator/index.php |
same sink, admin entry point | 200 (admin login redirect, no upload) | — |
Key code paths:
Central fix:
administrator/components/com_pagebuilderck/helpers/ckcontroller.php:230-232(execute()).Authorizer:
administrator/components/com_pagebuilderck/helpers/ckfof.php:50-57(checkSecurity/userCan).RCE sink:
administrator/components/com_pagebuilderck/helpers/ckbrowse.php:205-322(ajaxAddPicture).Controller method guard:
administrator/components/com_pagebuilderck/controllers/browse.php:38-47.Site dispatch entry:
site/pagebuilderck.php(always callsexecute()).Package / component:
com_pagebuilderck(Page Builder CK), free variant.Affected versions (as tested): fixed version 3.6.1 (no bypass); vulnerable state ≈ ≤ 3.5.10 (RCE reproduced).
Risk level: Critical (CVSS 4.0 Base 10.0) for the underlying CVE on vulnerable versions; the fix reduces unauthenticated RCE to a hard 403.
Consequences on vulnerable versions: unauthenticated full RCE (web shell upload + execution as
www-data). On the fixed version: blocked.
Impact Parity
- Disclosed / claimed maximum impact (parent CVE): unauthenticated arbitrary file upload → full Remote Code Execution (CVSS 10.0, CWE-434/284).
- Reproduced impact from this variant run:
- On the vulnerable state: full RCE confirmed —
browse.ajaxAddPicturereturned HTTP 200 and the uploadedexploit_shell.phpexecuted, emittingRCE_PROOF_Linux <host> … x86_64_www-data(web shell atmedia/com_pagebuilderck/gfonts/exploit_shell.php). - On the fixed state: no variant reached the sink; all controller-task probes returned 403 (or non-RCE 500); zero
.phpfiles appeared in the media tree; noRCE_PROOF_marker. No bypass.
- On the vulnerable state: full RCE confirmed —
- Parity:
nonefor a bypass (no bypass exists). For the parent claim on the vulnerable version, parity isfull(RCE). The alternate triggers are not RCE-capable (forced safe extensions / DB-only), so they do not attain the parent's code-execution impact. - Not demonstrated: No alternate path to code execution on the fixed version was demonstrated — because none exists for this root cause.
Root Cause
The underlying bug is a missing authorization check on com_pagebuilderck ajax controller endpoints. CKBrowse::ajaxAddPicture() accepts a multipart file upload and writes it with file_put_contents(JPATH_SITE . $imgpath . $filename, $object->data), where $imgpath (destination folder) and $filename (including extension — the getExt allowlist is commented out) are attacker-controlled, and the only pre-fix gate is a guest-harvestable CSRF token. The fix adds CKFof::checkSecurity() (→ $user->authorise('core.edit', …), false for guests → 403) in CKController::execute() for every non-display task and in each method. The same root-cause class (missing authz) exists on other ajax endpoints in the vulnerable version, but only ajaxAddPicture accepts an attacker-controlled file extension, so only it can deliver a .php web shell → RCE. No fix commit SHA is available (the extension is distributed as a package, not via a public git repository).
Reproduction Steps
- Reference:
bundle/vuln_variant/reproduction_steps.sh(idempotent; run from any directory). - What the script does:
- Starts Joomla 5.2 + MariaDB 10.11 in an isolated Docker compose project (
pbckvariant, port 38081) so it never collides with the repro environment. - Installs Joomla via CLI, then installs the fixed Page Builder CK v3.6.1 from
bundle/pagebuilderck_3.6.1.zip. - Stages a PHP web shell (
<?php echo "RCE_PROOF_".php_uname()."_".get_current_user(); ?>) and a container-sideprobe.shthat harvests a guest CSRF token from the public home page and fires unauthenticated requests. - Phase 1 (FIXED): fires 10 variant probes (C1–C10 above) against the unmodified fixed install and scans the media tree for any new
.php/RCE_PROOF_marker. - Phase 2 (VULNERABLE): backs up the fixed files, removes every
CKFof::checkSecurity()call (simulating ≤ 3.5.10), applies the Joomla 5.2$_FILEScompatibility patch, restarts the Joomla container to clear PHP OPcache so the reverted code is actually served, re-runs the probes, then restores the fixed files and tears down the containers. - Writes
validation_verdict.json,variant_manifest.json,runtime_manifest.json,source_identity.json,root_cause_equivalence.json, andphase_results.json. - Exit 0 = bypass on fixed; exit 1 = no bypass (the observed outcome).
- Starts Joomla 5.2 + MariaDB 10.11 in an isolated Docker compose project (
- Expected evidence:
bundle/logs/vuln_variant_evidence.log(full probe log),bundle/vuln_variant/phase_results.json(per-test HTTP codes),bundle/logs/fixed_version.txt(tested version). The decisive evidence: on the fixed versionrce_marker_phase1 = noneand all controller-task probes are 403; on the vulnerable versionrce_marker_phase2 = …/exploit_shell.phpand C1 returns 200 with theRCE_PROOF_marker.
Evidence
- Log:
bundle/logs/vuln_variant_evidence.log— full timestamped probe output for both phases. - Per-test HTTP codes:
bundle/vuln_variant/phase_results.json:- Phase 1 (fixed): C1=403, C2=403, C3=403, C4=403, C5=403, C6=500, C7=403, C8=403, C9=403, C10=200;
rce_marker_phase1 = none. - Phase 2 (vulnerable): C1=200, C2=200, C4=403, C5=200, C6=500, C9=200;
rce_marker_phase2 = /var/www/html/media/com_pagebuilderck/gfonts/exploit_shell.php.
- Phase 1 (fixed): C1=403, C2=403, C3=403, C4=403, C5=403, C6=500, C7=403, C8=403, C9=403, C10=200;
- Key excerpt (vulnerable RCE):
[P2-C1] shell access on vulnerable: RCE_PROOF_Linux 8c02f7b0fa95 7.0.14-arch1-1 … x86_64_www-dataandRCE marker file under media (Phase 2): /var/www/html/media/com_pagebuilderck/gfonts/exploit_shell.php. - Key excerpt (fixed blocked):
[P1-C1] RESULT|…|http_code=403;PHP files in media tree after Phase 1: 0;RCE marker file under media (Phase 1): none. - Environment: Joomla 5.2, PHP 8.2, Apache 2.4, MariaDB 10.11, Page Builder CK 3.6.1 (free). Tested source identity recorded in
bundle/vuln_variant/source_identity.json.
Recommendations / Next Steps
- For the RCE root cause: no gap to close — the fix is complete. The Coding stage need not extend the authorization fix for a bypass (none exists).
- Defense-in-depth (recommended, not required for correctness):
- Enforce an extension allowlist in
CKBrowse::ajaxAddPicture()(fix the commented-outgetExtcheck) so a.phpupload is rejected at the sink even if an authz check is ever regressed. - Constrain the
pathparameter to the configured images directory (reject..and absolute paths) inajaxAddPictureandajaxCreateFolder. - Add
CKFof::checkSecurity()tofrontedition::createModule()/savemodules()method bodies for consistency (they currently rely solely on the centralexecute()check). - Do not treat
CKFile::makeSafe()as a security boundary — it sanitizes filename characters but does not block executable extensions.
- Enforce an extension allowlist in
Additional Notes
- Idempotency: the script was executed twice end-to-end; both runs returned exit 1 with identical per-test HTTP codes and identical verdict (
bypass_on_fixed=false, vuln_rce=true), and the Docker compose project (pbckvariant) is torn down (down -v) at the end of every run, leaving no running containers and no changes to the project cache / repo checkout. - OPcache caveat: PHP OPcache initially served stale (fixed) bytecode after the on-disk
sedrevert in Phase 2, causing the first vulnerable-state run to still return 403. The script now restarts the Joomla container after the revert to flush OPcache; agrepconfirmscheckSecurityis gone fromckcontroller.phpbefore probing. Reproducers of this extension on Joomla 5.x should account for OPcache when hot-editing extension PHP. - Bounded search: fewer than three RCE-capable candidates exist because
ajaxAddPictureis the only sink accepting attacker-controlled file extensions; the remaining candidates were surveyed and ruled out as RCE paths (forced safe extensions / DB-only writes), so no additional attempts would yield a bypass for this root cause.
CVE-2026-56290 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-56290
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-56290
FAQ: CVE-2026-56290
How does the CVE-2026-56290 upload-to-RCE attack work?
browse.ajaxAddPicture with a PHP web shell as the uploaded 'picture' and an attacker-chosen destination path/filename, causing the extension to write the shell into a writable directory on the server, which the attacker then requests directly over HTTP to execute.Which Page Builder CK versions are affected by CVE-2026-56290, and where is it fixed?
CKFof::checkSecurity() authorization checks to the upload controller.How severe is CVE-2026-56290?
How can I reproduce CVE-2026-56290?
browse.ajaxAddPicture, and requests it over HTTP to confirm code execution.References for CVE-2026-56290
Authoritative sources for CVE-2026-56290 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.