{"repro_id":"REPRO-2026-00273","version":6,"title":"Vtiger CRM through 8.4.0 allows authenticated admin users to achieve remote code execution by uploading a crafted module ZIP that places PHP files in the web-accessible modules/ directory.","repro_type":"security","status":"published","severity":"high","cvss_score":8.6,"description":"Vtiger CRM through 8.4.0 contains an authenticated remote code execution (RCE) flaw in the admin Module Manager import feature. A crafted module ZIP can place arbitrary PHP files into the web root (`modules/`), which are then directly reachable via HTTP, bypassing application routing and authentication.","root_cause":"# Root Cause Analysis: GHSA-jf2j-jhvf-rc56 / CVE-2026-23698\n\n## Summary\n\nVtiger CRM 8.4.0 (and earlier 8.x versions) allows an authenticated administrator to upload a crafted module ZIP archive through the **Module Manager → Import Module** endpoint. The upload handler accepts any ZIP that contains a valid `manifest.xml`, and the installer extracts the entire archive under the web-root `modules/` directory. Because there is no file-type/content validation, an attacker can include an executable PHP file in the ZIP. Once extracted, the PHP file is reachable directly via the web server (e.g. `/modules/<moduleName>/shell.php`) and executes outside of Vtiger’s routing/authentication layer, giving the attacker a persistent web shell and arbitrary remote code execution.\n\n## Impact\n\n- **Product/component**: Vtiger CRM (open-source PHP/MySQL CRM)\n- **Affected versions**: through 8.4.0 (tested on 8.4.0; the Module Manager import code path exists across the 8.x line)\n- **Risk level**: High\n- **Consequences**: A low-privilege administrator with access to the Module Manager can install arbitrary PHP into the web root, bypassing the application’s authentication/authorization boundary and achieving persistent, unauthenticated remote code execution as the web-server user (`www-data`).\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact**: Authenticated remote code execution via a crafted module ZIP (code execution).\n- **Reproduced impact from this run**: The reproduction script logged into the real Vtiger 8.4.0 application, uploaded a malicious ZIP through the Module Manager endpoint, completed the import, and then accessed `http://localhost/modules/<module>/shell.php?cmd=whoami`. The response contained the marker `PRUVA_SHELL_OK` and the output `www-data`, demonstrating that attacker-controlled PHP was executed.\n- **Parity**: `full` — the reproduced impact matches the claimed RCE impact.\n- **Not demonstrated**: None.\n\n## Root Cause\n\nThe vulnerability is in the **Module Manager import** workflow:\n\n1. **Upload** (`modules/Settings/ModuleManager/views/ModuleImport.php::importUserModuleStep2`) saves the uploaded ZIP to `test/vtlib/` and only validates that `manifest.xml` exists and declares a valid module name and Vtiger version compatibility. It does not inspect the file list or file extensions inside the ZIP.\n2. **Installation** (`modules/Settings/ModuleManager/actions/Basic.php::importUserModuleStep3`) calls `Vtiger_Package::import()` on the saved ZIP, which extracts all contents under the web root, including `modules/<ModuleName>/` and `languages/en_us/`.\n3. **Direct execution**: Apache resolves `/modules/<ModuleName>/shell.php` directly, invoking the PHP interpreter before Vtiger’s `index.php` routing or authentication checks. Any PHP file placed in the ZIP is therefore executable by anyone who knows its URL.\n\nThe architectural gap is that Vtiger treats `modules/` as both an application-code directory and an import target, with no allowlist of file types or relocation of uploaded module contents outside the web root. The ticket/advisory notes that the only validation is the manifest XML, satisfying the file-type check for CWE-434.\n\n**Fix commit**: The advisory lists patched versions as unknown, and no specific fix commit was supplied. The remediation should add a content-type/allowlist check before extraction (e.g., only `.php`, `.tpl`, `.js`, `.css`, `.xml`, `.png`, `.jpg`, etc., and reject PHP files that are not legitimate module class files, or move extracted modules outside the web root and route them through Vtiger’s authenticated front controller).\n\n## Reproduction Steps\n\nThe full, automated reproduction is in `bundle/repro/reproduction_steps.sh`. At a high level it:\n\n1. Installs PHP 8.5, MariaDB, Apache, and Python tools in the sandbox.\n2. Downloads Vtiger CRM 8.4.0 from SourceForge and deploys it to `/var/www/html`.\n3. Creates a fresh MariaDB database and user, then runs the Vtiger web installer automatically.\n4. Logs in as the admin user and completes the first-time System/User setup.\n5. Builds a malicious ZIP with a valid `manifest.xml`, a boilerplate language file, and `modules/<module>/shell.php`.\n6. Navigates to **Module Manager → Import Module**, uploads the ZIP, and completes the import.\n7. Accesses the extracted PHP file directly via HTTP with `?cmd=whoami` and captures the output.\n\n**Expected evidence of reproduction**: The webshell responds with the marker `PRUVA_SHELL_OK` and the output of the injected command (e.g., `www-data`).\n\n## Evidence\n\n- `bundle/logs/reproduction_steps.log` — full setup, install, and exploit trace.\n- `bundle/repro/proof.log` — captured webshell output for the last run.\n- `bundle/repro/runtime_manifest.json` — runtime evidence manifest (services, proof artifacts, endpoint).\n- `bundle/artifacts/malmod_<module>/malmod.zip` — the crafted module ZIP used in the successful run.\n\nKey excerpt from `bundle/repro/proof.log` (second run):\n\n```\nPRUVA_SHELL_OK\nwww-data\nwww-data\n```\n\nKey excerpt from `bundle/logs/reproduction_steps.log` showing the install response and trigger:\n\n```\n[+] Upload accepted: file=usermodule_1783465710.zip name=ModSiUWLcoD type=module\n[*] Installing the module\n[+] Install response: {\"success\":true,\"result\":{\"success\":true,\"importModuleName\":\"ModSiUWLcoD\"}}\n[*] Triggering webshell: http://localhost/modules/ModSiUWLcoD/shell.php?cmd=whoami\n[+] Webshell output: 200 PRUVA_SHELL_OK\nwww-data\nwww-data\n```\n\nEnvironment captured during the run:\n\n- Vtiger CRM 8.4.0 (tarball from SourceForge)\n- Apache 2.4.66 + PHP 8.5 (mod_php)\n- MariaDB 11.8.6\n- Ubuntu 26.04 (resolute)\n\n## Recommendations / Next Steps\n\n- **Immediate fix**: Before calling `Vtiger_Package::import()`, enumerate the ZIP contents and reject the archive if it contains any file outside a strict allowlist of module file types (e.g., legitimate PHP class/ action/ view files, templates, language files, assets). In particular, reject arbitrary PHP files that do not belong to the module’s expected class hierarchy.\n- **Architectural fix**: Do not extract user-supplied module archives directly into the web-root `modules/` directory. Instead, stage them outside the document root and only copy validated files into the application tree, or serve module assets through a non-executable endpoint.\n- **Upgrade guidance**: Until an official patch is released, restrict admin access and monitor the `test/vtlib/` and `modules/` directories for unexpected PHP files.\n- **Testing**: Add a regression test that uploads a ZIP containing a `manifest.xml` plus an arbitrary PHP file and asserts that the import is rejected and the PHP file is never created under `modules/`.\n\n## Additional Notes\n\n- **Idempotency**: The reproduction script was run twice consecutively in the same environment and succeeded both times. Each run uses a fresh database and a unique module name to avoid collisions with previously imported modules, so it is safe to run repeatedly.\n- **Limitations**: The reproduction requires the Vtiger 8.4.0 installer to create the database schema, which is a synchronous, long-running operation; the script waits up to 10 minutes for the installer to complete.\n- **No sanitizer**: The primary proof was produced against the real product without ASAN/UBSAN; the success oracle is the actual webshell response.\n","ghsa_id":"GHSA-JF2J-JHVF-RC56","cve_id":"CVE-2026-23698","cwe_id":"CWE-434 (Unrestricted Upload of File with Dangerous Type)","source_url":"https://github.com/advisories/GHSA-jf2j-jhvf-rc56","package":{"name":"Vtiger CRM","ecosystem":"other","affected_versions":"through 8.4.0"},"reproduced_at":"2026-07-08T04:52:19.130010+00:00","duration_secs":2034.0,"tool_calls":237,"handoffs":2,"total_cost_usd":3.4013369500000032,"agent_costs":{"hypothesis_generator":0.01125045,"judge":0.234944,"repro":2.228982460000001,"support":0.04705726,"vuln_variant":0.87910278},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/kimi-k2p7-code":0.01125045},"judge":{"gpt-5.5":0.234944},"repro":{"accounts/fireworks/models/kimi-k2p7-code":2.228982460000001},"support":{"accounts/fireworks/models/kimi-k2p7-code":0.04705726},"vuln_variant":{"accounts/fireworks/models/kimi-k2p7-code":0.87910278}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-08T04:52:39.354398+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":11484,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":7343,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":8977,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":10414,"category":"analysis"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":7116,"category":"other"},{"path":"bundle/artifact_promotion_report.json","filename":"artifact_promotion_report.json","size":9961,"category":"other"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":938,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":2435,"category":"other"},{"path":"bundle/repro/proof.log","filename":"proof.log","size":32,"category":"log"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":1500,"category":"log"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":681,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":824,"category":"other"},{"path":"bundle/logs/vuln_variant_reproduction_steps.log","filename":"vuln_variant_reproduction_steps.log","size":1403,"category":"log"},{"path":"bundle/vuln_variant/proof.log","filename":"proof.log","size":42,"category":"log"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3445,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":3402,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":872,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":7346,"category":"documentation"}]}