Veeam has recently released an advisory for CVE-2023-27532 for Veeam Backup and Replication which allows an unauthenticated user with access to the Veeam backup service (TCP 9401 by default) to request cleartext credentials. Other’s have provides insight into this vulnerability including Huntress, Y4er, and CODE WHITE. In this post we hope to provide additional insight an would also like to release our POC (found here) which is built on .NET core and capable of running on Linux.
We first determine what application is using vulnerable port 9401.
Now we can begin to reverse engineer the Veeam Backup Service. By default, we find the service binary and its associated assemblies in C:\Program Files\Veeam\Backup and Replication\Backup
. The log file for the backup service is located at C:\ProgramData\Veeam\Backup\Svc_VeeamBackup.log
. Searching for the port in question in the log file gives a string that we can search for in the binary.
Next we dig into the call to CreateService
and eventually find ourselves at a private constructor for CRemoteInvokeServiceHolder
:
The use of ServiceHost
, NetTcpBinding
, and AddServiceEndpoint
gives us enough context to know that this app is hosting a Windows Communication Foundation (WCF) service. The services exposes the IRemoteInvokeService
interface to the client and the interface is implemented by CVbRestoreServiceStub
on the server side.
The use of NetTcpBinding
tells us that this service is using a binary protocol built on TCP intended for WCF to WCF communication. This restricts our client implementation to a .NET language as it would be rather difficult to write a custom WCF binary parser. We do not want our POC to be restricted to running on Windows so we will use .NET core for our POC.
Before we are able to create a client, we need the service interface definition. Based on previous research, we know that we need a few credential based methods from the DatabaseManager
scope. We end up with the following definition:
Now we can try to connect to the service:
Our first attempt is met with the following error:
/home/dev/RiderProjects/Veeam_CVE-2023-27532/CVE-2023-27532/bin/Debug/net6.0/CVE-2023-27532 net.tcp://192.168.1.139:9401/ Unhandled exception. System.ServiceModel.ProtocolException: The requested upgrade is not supported by 'net.tcp://192.168.1.139:9401/'. This could be due to mismatched bindings (for example security enabled on the client and not on the server).
If we look back at CRemoteInvokeServiceHolder
, we see that the NetTcpBinding
was created with parameter "invokeServiceBinding"
. We can find the configuration parameters for this binding in Veeam.Backup.Service.exe.config
:
With this information, we can update our POC. After disabling certificate validation and setting the correct DNSIdentity
, we have the following:
Running the POC we get:
/home/dev/RiderProjects/Veeam_CVE-2023-27532/CVE-2023-27532/bin/Debug/net6.0/CVE-2023-27532 net.tcp://192.168.1.139:9401/ Unhandled exception. System.ServiceModel.FaultException: Data at the root level is invalid. Line 1, position 1.
This indicates that we were able to successfully invoke the service. The error is a result of us passing invalid XML. Now we can begin to figure out how to extract credentials from this API.
Based on previous research, we know that we can invoke CredentialsDbScopeGetAllCreds
to get a binary blob containing credential information. If we look at the implementation of ExecuteCredentialsDbScopeGetAllCreds
we see that this blob is a serialized C# object created by Veeam’s custom CProxyBinaryFormatter
.
If we dump this data to a file we can see that there is username and password information, but it is not immediately obvious how to parse this information out of the binary blob. We don’t want to reimplement Veeam’s custom serialization class and also don’t want to have to reference their assemblies. So what can we do? It looks like there are some easily parseable guids prefixed by $
.
We see that these guids match the id used for Credentials in the Veeam database:
We can now use the CredentialsDbScopeFindCredentials
endpoint to get one credential at a time. Our code to extract the guids looks like:
The output from CredentialsDbScopeFindCredentials
is still a binary blob, but at least we can work with one Credential at a time now instead of a list. We still have the problem of having to parse out the usernames and passwords. Luckily, we found a Stackoverflow post detailing how to use a custom serializer to get object properties as key value pairs. We can now extract the usernames an passwords with ease.
Running our POC we get the following output:
/home/dev/RiderProjects/Veeam_CVE-2023-27532/CVE-2023-27532/bin/Debug/net6.0/CVE-2023-27532 net.tcp://192.168.1.139:9401/ UserName = dev Password = Super Secret Password UserName = root Password = UserName = root Password = UserName = root Password = UserName = root Password =
In conclusion, CVE-2023-27532 allows an unauthenticated user with access to the Veeam backup service to request cleartext credentials. We have examined the vulnerable port, reverse engineered the Veeam Backup Service, and constructed a WCF client using .NET core. We have also shown how to extract credentials from the Veeam database by invoking the CredentialsDbScopeGetAllCreds and CredentialsDbScopeFindCredentials endpoints. Finally, we have released our POC on Github, which is built on .NET core and capable of running on Linux, making it accessible to a wider audience. It is important to note that this vulnerability should be taken seriously and patches should be applied as soon as possible to ensure the security of your organization.