{"repro_id":"REPRO-2026-00259","version":7,"title":"iCagenda unauthenticated file upload RCE in public event submission form","repro_type":"security","status":"published","severity":"critical","description":"The frontend public event submission form in iCagenda allows unauthenticated users to upload arbitrary files via the attachment field. Files are stored under /images/icagenda/frontend/attachments/ with their original extension and no extension/MIME/content validation. On Joomla 6 the uploaded PHP file is executable, yielding unauthenticated RCE.","root_cause":"## Summary\n\nCVE-2026-48939 is an unauthenticated arbitrary PHP file upload vulnerability in the iCagenda Joomla extension public event submission workflow. In iCagenda 4.0.7, a guest can submit the public event form with a PHP file in the attachment field. The component stores the attachment, preserving a PHP extension, under Joomla's web-accessible `images/icagenda/frontend/attachments/` directory. On a Joomla 6 / Apache PHP deployment, requesting the uploaded `.php` file over HTTP executes attacker-controlled PHP code. The reproduction script demonstrates this end-to-end and compares it with iCagenda 4.0.8, which blocks the same PHP upload.\n\n## Impact\n\n- **Package/component affected:** iCagenda Joomla extension (`com_icagenda`), specifically the frontend public event submission attachment handling.\n- **Affected versions:** The ticket identifies iCagenda 3.2.1 through 4.0.7, also described as 1.0.0 through 4.0.7. This run validated iCagenda **4.0.7** as vulnerable.\n- **Fixed versions:** iCagenda **4.0.8** for the 4.x branch and 3.9.15 for the legacy branch, per the ticket/advisory.\n- **Risk level and consequences:** Critical. An unauthenticated remote attacker can upload a PHP web shell through the public event submission form and then execute commands in the web server context (`www-data` in the reproduced Docker deployment). This can lead to full compromise of the Joomla site, disclosure or modification of site data, persistence, and lateral movement depending on host configuration.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** unauthenticated remote code execution via arbitrary PHP upload through the public iCagenda event submission form.\n- **Reproduced impact from this run:** full unauthenticated PHP code execution. The vulnerable run uploaded `pruva-rce-vulnerable.php` through the real public iCagenda event submit endpoint and then fetched `/images/icagenda/frontend/attachments/pruva-rce-vulnerable.php?cmd=id`. The HTTP response contained the attacker-controlled PHP marker and command output: `uid=33(www-data) gid=33(www-data) groups=33(www-data)`.\n- **Parity:** `full`.\n- **Not demonstrated:** No privilege escalation beyond the web server user was attempted; the reproduced impact is remote command execution as the Joomla/Apache PHP process user.\n\n## Root Cause\n\nIn iCagenda 4.0.7, the frontend submit model reads uploaded files from the public event form and passes the attachment directly to `frontendFileUpload()`:\n\n- `SubmitModel::submit()` obtains `$files = $jinput->files->get('jform')` and `$file = $files['file']`.\n- For a non-empty attachment, it sets `$eventData->file = $this->frontendFileUpload($file)`.\n- `frontendFileUpload($file)` derives a slugged filename while preserving the original extension, creates `images/icagenda/frontend/attachments/`, and calls `Joomla\\Filesystem\\File::upload($src, $dest, false)` with a destination below the web root.\n\nOn Joomla 6, the framework `Joomla\\Filesystem\\File::upload()` moves the uploaded file but does not perform CMS media allow-list validation. Therefore, a `.php` attachment remains a `.php` file and is served/executed by Apache/PHP from the web-accessible attachments directory.\n\nThe fixed iCagenda 4.0.8 code adds Joomla media validation before storing attachments. The local source evidence captured by the script shows 4.0.8 importing and invoking `Joomla\\CMS\\Helper\\MediaHelper` / `MediaHelper::canUpload($file)` before `frontendFileUpload()`. In the fixed runtime, the same PHP attachment is not written and the shell URL returns HTTP 404.\n\nNo specific fix commit hash was provided in the ticket. The validated fixed artifact is the official iCagenda 4.0.8 package.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh`.\n2. The script:\n   - starts real Docker product stacks for Joomla 6.0 / PHP 8.3 / Apache and MySQL 8.0;\n   - installs official iCagenda 4.0.7 in the vulnerable stack and official iCagenda 4.0.8 in the fixed stack;\n   - configures a public iCagenda submit menu and enables the file attachment field;\n   - fetches the public event submission form and extracts the CSRF token;\n   - submits an unauthenticated multipart form upload containing a PHP shell through `index.php?option=com_icagenda&task=submit.submit`;\n   - requests the uploaded PHP artifact over HTTP with `?cmd=id`; and\n   - repeats the same path against iCagenda 4.0.8 as a fixed-version negative control.\n3. Expected evidence:\n   - vulnerable iCagenda 4.0.7 stores `pruva-rce-vulnerable.php` under `images/icagenda/frontend/attachments/`;\n   - HTTP GET to the uploaded PHP file returns `PRUVA_PHP_SHELL_ACTIVE_VULNERABLE` and command output from `id` as `www-data`;\n   - fixed iCagenda 4.0.8 does not store `pruva-rce-fixed.php` and the shell URL returns HTTP 404.\n\n## Evidence\n\nPrimary runtime artifacts from the successful run:\n\n- `bundle/logs/reproduction_steps.log` — full product deployment, extension installation, form submission, uploaded PHP execution, and fixed negative control log.\n- `bundle/repro/fetch_vulnerable_rce.log` — HTTP response from the uploaded PHP shell in the vulnerable deployment.\n- `bundle/repro/fetch_fixed_rce.log` — HTTP 404 response for the same shell path in the fixed deployment.\n- `bundle/repro/upload_vulnerable.log` — HTTP response to the unauthenticated vulnerable form submission.\n- `bundle/repro/upload_fixed.log` — HTTP response to the fixed-version form submission.\n- `bundle/repro/submitted_files_vulnerable.txt` — container-side listing showing `pruva-rce-vulnerable.php` saved in the attachments directory.\n- `bundle/repro/submitted_files_fixed.txt` — fixed-version listing showing the attachment directory was absent / PHP shell not stored.\n- `bundle/logs/vulnerable_source.log` and `bundle/logs/fixed_source.log` — runtime source/version evidence from inside each Joomla container.\n- `bundle/repro/runtime_manifest.json` — structured runtime evidence manifest written by the script.\n\nKey vulnerable evidence excerpt from `bundle/logs/reproduction_steps.log` and `bundle/repro/fetch_vulnerable_rce.log`:\n\n```text\nvulnerable: public form reached with Itemid=112, catid=1, CSRF=...\nvulnerable: submitting unauthenticated event form with PHP attachment pruva-rce-vulnerable.php\nindex.html 47 bytes\npruva-rce-vulnerable.php 258 bytes\nvulnerable: requesting uploaded PHP URL http://172.20.0.1:18139/images/icagenda/frontend/attachments/pruva-rce-vulnerable.php?cmd=id\nHTTP/1.1 200 OK\nPRUVA_PHP_SHELL_ACTIVE_VULNERABLE\nPRUVA_ATTACKER_COMMAND_OUTPUT_BEGIN\nuid=33(www-data) gid=33(www-data) groups=33(www-data)\nPRUVA_ATTACKER_COMMAND_OUTPUT_END\n```\n\nKey fixed-version negative-control excerpt:\n\n```text\nfixed: public form reached with Itemid=112, catid=1, CSRF=...\nfixed: submitting unauthenticated event form with PHP attachment pruva-rce-fixed.php\nattachments directory absent\nfixed: requesting uploaded PHP URL http://172.20.0.1:18140/images/icagenda/frontend/attachments/pruva-rce-fixed.php?cmd=id\nHTTP/1.1 404 Not Found\nFixed iCagenda 4.0.8 negative control passed: PHP shell was not stored/executed\n```\n\nEnvironment details captured by the script include the Docker images `mysql:8.0` and `joomla:6.0-php8.3-apache`, Apache/PHP version output in the source logs, and iCagenda package version evidence.\n\n## Recommendations / Next Steps\n\n- Upgrade iCagenda to **4.0.8** or later (or **3.9.15** or later for legacy 3.x deployments).\n- Ensure all frontend file upload paths enforce server-side allow-list validation for extension, MIME type, and file content before saving files.\n- Store user-uploaded files outside executable web roots where possible; alternatively configure the web server to disable PHP execution in upload directories such as `images/icagenda/frontend/attachments/`.\n- Add regression tests that submit disallowed extensions (`.php`, `.phtml`, `.phar`) through the public event form and assert that the files are neither stored nor executable.\n- Consider scanning existing `images/icagenda/frontend/attachments/` directories for unexpected PHP or other executable files on previously exposed sites.\n\n## Additional Notes\n\n- The generated reproduction script was run twice consecutively and passed both times after the install-path adjustment.\n- The proof uses the real product and the public HTTP/API boundary: a running Joomla service, official iCagenda packages, a public event submission form, a multipart upload, and an HTTP request to the uploaded artifact.\n- The RCE behavior depends on a Joomla 6 / Apache PHP deployment where PHP files under the images directory are executable. This matches the public PoC/advisory description that the uploaded `.php` files execute on Joomla 6.\n- No sanitizer or mock harness was used; `sanitizer_used=false` is appropriate for the runtime verdict.\n","cve_id":"CVE-2026-48939","cwe_id":"CWE-284 Improper Access Control and CWE-434 Unrestricted Upload of File with Dangerous Type","source_url":"https://www.joomlic.com/download/icagenda","package":{"name":"iCagenda","ecosystem":"joomla","affected_versions":"iCagenda 3.2.1 through 3.9.14 and 4.0.0 through 4.0.7 (ticket also notes 1.0.0 through 4.0.7)","fixed_version":"3.9.15 / 4.0.8","tested_patched":"4.0.8"},"reproduced_at":"2026-07-06T18:12:37.867271+00:00","duration_secs":6437.0,"tool_calls":597,"handoffs":4,"total_cost_usd":18.758036229999988,"agent_costs":{"coding":0.50363546,"hypothesis_generator":0.03091135,"judge":0.912615,"repro":13.569117149999997,"support":0.03446127,"vuln_variant":3.707295999999999},"cost_breakdown":{"coding":{"accounts/fireworks/models/kimi-k2p7-code":0.50363546},"hypothesis_generator":{"accounts/fireworks/models/kimi-k2p7-code":0.03091135},"judge":{"gpt-5.5":0.912615},"repro":{"accounts/fireworks/models/kimi-k2p7-code":8.129385150000001,"gpt-5.5":5.439732},"support":{"accounts/fireworks/models/kimi-k2p7-code":0.03446127},"vuln_variant":{"gpt-5.5":3.707295999999999}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-06T18:13:18.972368+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":18379,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8795,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":17461,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":11503,"category":"analysis"},{"path":"bundle/coding/proposed_fix.diff","filename":"proposed_fix.diff","size":1740,"category":"patch"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":16029,"category":"other"},{"path":"bundle/artifact_promotion_report.json","filename":"artifact_promotion_report.json","size":15813,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":1372,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":1470,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1149,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":7103,"category":"log"},{"path":"bundle/repro/fetch_vulnerable_18097.log","filename":"fetch_vulnerable_18097.log","size":67,"category":"log"},{"path":"bundle/repro/fetch_vulnerable_rce.log","filename":"fetch_vulnerable_rce.log","size":356,"category":"log"},{"path":"bundle/repro/fetch_fixed_rce.log","filename":"fetch_fixed_rce.log","size":4572,"category":"log"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":801,"category":"other"},{"path":"bundle/logs/vulnerable_source.log","filename":"vulnerable_source.log","size":448,"category":"log"},{"path":"bundle/logs/fixed_source.log","filename":"fixed_source.log","size":640,"category":"log"},{"path":"bundle/logs/vulnerable_container.log","filename":"vulnerable_container.log","size":4352,"category":"log"},{"path":"bundle/logs/fixed_container.log","filename":"fixed_container.log","size":2437,"category":"log"},{"path":"bundle/repro/form_page_vulnerable.html","filename":"form_page_vulnerable.html","size":53717,"category":"other"},{"path":"bundle/repro/form_page_fixed.html","filename":"form_page_fixed.html","size":53734,"category":"other"},{"path":"bundle/repro/upload_vulnerable.log","filename":"upload_vulnerable.log","size":10698,"category":"log"},{"path":"bundle/repro/upload_fixed.log","filename":"upload_fixed.log","size":4526,"category":"log"},{"path":"bundle/logs/vuln_variant/reproduction_steps.log","filename":"reproduction_steps.log","size":9842,"category":"log"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":7899,"category":"documentation"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3657,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":2247,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":1567,"category":"other"},{"path":"bundle/logs/vuln_variant/vulnerable_source.log","filename":"vulnerable_source.log","size":600,"category":"log"},{"path":"bundle/logs/vuln_variant/fixed_source.log","filename":"fixed_source.log","size":787,"category":"log"},{"path":"bundle/logs/vuln_variant/vulnerable_version.txt","filename":"vulnerable_version.txt","size":600,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_version.txt","filename":"fixed_version.txt","size":787,"category":"other"},{"path":"bundle/vuln_variant/form_page_vulnerable.html","filename":"form_page_vulnerable.html","size":53719,"category":"other"},{"path":"bundle/vuln_variant/form_page_fixed.html","filename":"form_page_fixed.html","size":53736,"category":"other"},{"path":"bundle/vuln_variant/upload_image_vulnerable.log","filename":"upload_image_vulnerable.log","size":10702,"category":"log"},{"path":"bundle/vuln_variant/upload_image_fixed.log","filename":"upload_image_fixed.log","size":10702,"category":"log"},{"path":"bundle/vuln_variant/fetch_image_vulnerable_php.log","filename":"fetch_image_vulnerable_php.log","size":4574,"category":"log"},{"path":"bundle/vuln_variant/fetch_image_vulnerable_gif.log","filename":"fetch_image_vulnerable_gif.log","size":428,"category":"log"},{"path":"bundle/vuln_variant/fetch_image_fixed_php.log","filename":"fetch_image_fixed_php.log","size":4574,"category":"log"},{"path":"bundle/vuln_variant/fetch_image_fixed_gif.log","filename":"fetch_image_fixed_gif.log","size":423,"category":"log"},{"path":"bundle/vuln_variant/submitted_images_vulnerable.txt","filename":"submitted_images_vulnerable.txt","size":63,"category":"other"},{"path":"bundle/vuln_variant/submitted_images_fixed.txt","filename":"submitted_images_fixed.txt","size":58,"category":"other"},{"path":"bundle/coding/verify_fix.sh","filename":"verify_fix.sh","size":13842,"category":"other"},{"path":"bundle/coding/summary_report.md","filename":"summary_report.md","size":4517,"category":"documentation"},{"path":"bundle/logs/verify_fix.log","filename":"verify_fix.log","size":3140,"category":"log"}]}