metasploit-framework/modules/auxiliary/scanner/http/wordpress_scanner.rb

182 lines
6.6 KiB
Ruby

##
# 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::Scanner
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'Wordpress Scanner',
'Description' => 'Detects Wordpress Versions, Themes, Plugins, and Users',
'Author' => [
'Christian Mehlmauer', # original module
'h00die', # plugins and themes
'shoxxdj' # users
],
'License' => MSF_LICENSE
)
register_options [
OptBool.new('EXPLOITABLE', [false, 'Only scan plugins and themes which a MSF module exists for', true]),
OptPath.new('EXPLOITABLE_THEMES', [
true, 'File containing exploitable by MSF themes',
File.join(Msf::Config.data_directory, 'wordlists', 'wp-exploitable-themes.txt')
]),
OptPath.new('EXPLOITABLE_PLUGINS', [
true, 'File containing exploitable by MSF plugins',
File.join(Msf::Config.data_directory, 'wordlists', 'wp-exploitable-plugins.txt')
]),
OptBool.new('THEMES', [false, 'Detect themes', true]),
OptBool.new('PLUGINS', [false, 'Detect plugins', true]),
OptPath.new('THEMES_FILE', [
true, 'File containing themes to enumerate',
File.join(Msf::Config.data_directory, 'wordlists', 'wp-themes.txt')
]),
OptPath.new('PLUGINS_FILE', [
true, 'File containing plugins to enumerate',
File.join(Msf::Config.data_directory, 'wordlists', 'wp-plugins.txt')
]),
OptInt.new('PROGRESS', [true, 'how often to print progress', 1000]),
OptBool.new('USERS', [false, 'Detect users with API', true])
]
end
def print_progress(host, i, total)
print_status("#{host} - Progress #{i.to_s.rjust(Math.log10(total).ceil + 1)}/#{total} (#{((i.to_f / total) * 100).truncate(2)}%)")
end
def run_host(target_host)
print_status("Trying #{target_host}")
if wordpress_and_online?
version = wordpress_version
version_string = version || '(no version detected)'
print_good("#{target_host} - Detected Wordpress #{version_string}")
report_note(
{
host: target_host,
proto: 'tcp',
sname: (ssl ? 'https' : 'http'),
port: rport,
type: "Wordpress #{version_string}",
data: target_uri.to_s
}
)
if datastore['THEMES']
print_status("#{target_host} - Enumerating Themes")
if datastore['EXPLOITABLE']
f = File.open(datastore['EXPLOITABLE_THEMES'], 'rb')
else
f = File.open(datastore['THEMES_FILE'], 'rb')
end
total = f.readlines.size
f.rewind
f = f.readlines
f.each_with_index do |theme, i|
theme = theme.strip
print_progress(target_host, i, total) if i % datastore['PROGRESS'] == 0
vprint_status("#{target_host} - Checking theme: #{theme}")
version = check_theme_version_from_readme(theme)
next if version == Msf::Exploit::CheckCode::Unknown # aka not found
print_good("#{target_host} - Detected theme: #{theme} version #{version.details[:version]}")
report_note(
{
host: target_host,
proto: 'tcp',
sname: (ssl ? 'https' : 'http'),
port: rport,
type: "Wordpress Theme: #{theme} version #{version.details[:version]}"
# data: target_uri
}
)
end
print_status("#{target_host} - Finished scanning themes")
end
if datastore['PLUGINS']
print_status("#{target_host} - Enumerating plugins")
if datastore['EXPLOITABLE']
f = File.open(datastore['EXPLOITABLE_PLUGINS'], 'rb')
else
f = File.open(datastore['PLUGINS_FILE'], 'rb')
end
total = f.readlines.size
f.rewind
f = f.readlines
f.each_with_index do |plugin, i|
plugin = plugin.strip
print_progress(target_host, i, total) if i % datastore['PROGRESS'] == 0
vprint_status("#{target_host} - Checking plugin: #{plugin}")
version = check_plugin_version_from_readme(plugin)
next if version == Msf::Exploit::CheckCode::Unknown # aka not found
print_good("#{target_host} - Detected plugin: #{plugin} version #{version.details[:version]}")
report_note(
{
host: target_host,
proto: 'tcp',
sname: (ssl ? 'https' : 'http'),
port: rport,
type: "Wordpress Plugin: #{plugin} version #{version.details[:version]}"
# data: target_uri
}
)
end
print_status("#{target_host} - Finished scanning plugins")
end
if datastore['USERS']
print_status("#{target_host} - Searching Users")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(wordpress_url_rest_api, 'users')
})
if res.nil?
print_error('Error getting response.')
elsif res.code == 200
parsed = res.get_json_document
if parsed.empty?
print_error('Response received, but no JSON content was provided.')
else
parsed.map do |child|
name = child['name']
wp_username = child['slug']
print_good("#{target_host} - Detected user: #{name} with username: #{wp_username}")
service_data = {
address: rhost,
port: rport,
service_name: (ssl ? 'https' : 'http'),
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: wp_username,
private_data: '',
private_type: :password
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED,
proof: nil
}.merge(service_data)
create_credential_login(login_data)
end
print_status("#{target_host} - Finished scanning users")
end
else
print_status("#{target_host} - Was not able to identify users on site using #{wordpress_url_rest_api}/users")
end
print_status("#{target_host} - Finished all scans")
end
end
end
end