By Executive Order, We Are Banning Blacklists - Domain-Level RCE in Veeam Backup & Replication (CVE-2025-23120)

It’s us again!
Once again, we hear the collective groans - but we're back and with yet another merciless pwnage of an inspired and clearly comprehensive RCE solution - no, wait, it's another vuln in yet another backup and replication solution..
While we would enjoy a world in which we could be a little merciful - today we'll explore the painful world of blacklist-based security mechanisms. You can treat this post as a natural continuation of our CVE-2024-40711 writeup, which was written by fellow watchTowr Labs team member Sina Kheirkhah (@SinSinology).
Our previous watchTowr Labs post provided a detailed walk-through of a Remote Code Execution vulnerability RCE issue in Veeam Backup & Replication, achievable from an unauthenticated perspective. This vulnerability was discovered and responsibly disclosed by one of our favourite researchers from 'across the aisle' - Florian Hauser (@Frycos) with Code White Gmbh - and the RCE itself was interesting with twists included.
As an industry, we know that uncontrolled deserialization always leads to bad things. This is why one should always implement strict control of classes that are being deserialized. Ideally, it should be a whitelist, which allows the deserialization of selected classes only. Although technically, the Veeam solution in discussion does this, one of the allowed classes… leads to inner deserialization, which subsequently implements a blacklist-based check.
You can deserialize anything - except the ones that Veeam has decided are bad.
Blacklists (also known as block-lists or deny-lists) are based on a very optimistic (and provably flawed) idea that we can just make a list of all the bad classes, and we just keep a record of everything bad that can be done and update our list as and when new bad is introduced.
Luckily, as an industry, we actually already have a list of all the bad classes in the world, and so this is flawless logic. There are a couple of bitter truths though:
- This is a lie. While we can agree that nowadays it’s extremely hard to find new deserialization gadgets in programming languages and frameworks (although still possible), products have their own codebase and can contain abusable classes that can be misused during deserialization. This is before we even get on to 3rd party libraries.
Author’s note: There are many technical posts and papers about abusing the deserialization blacklists. I can shamelessly point you to my whitepaper and Hexacon talk, where I’ve been pwning blacklists through new deserialization gadgets discovered in .NET Framework, targeted product codebase and 3rd party libraries.
James Kettle said that this research “destroys any faith you might have had in blocklist-based deserialization mitigations”. While I personally love this quote and I couldn’t have described my research goal better, it seems that I’ve ultimately failed. Vendors are still using blacklists a lot, and I don’t believe it’s going to change very soon.
- The list of deserialization gadgets is actually pretty big - especially for Java and C#. We have routinely shown, privately and publicly, that we can abuse blacklist-protected deserialization sinks with commonly known gadget.
Anyway, you've probably guessed where this is going today - it seems Veeam, despite being a ransomware gang's favourite play toy - didn’t learn after the lesson given by Frycos in previous research published. You guessed it - they fixed the deserialization issues by adding entries to their deserialization blacklist.
We're bored, we're restless - so, we decided to have a quick look at the current state of their list to see if we could find a way to add our mark.
In this blog post, we will show you 2 Remote Code Execution vulnerabilities in the Veeam Backup & Response solution, which are based on very similar deserialization gadgets existing in the Veeam codebase.
These vulnerabilities can be exploited by any user who belongs to the local users group on the Windows host of your Veeam server. Better yet - if you have joined your server to the domain, these vulnerabilities can be exploited by any domain user.
I know that you, dear reader, do not join your Veeam Backup & Replication backup server to your Active Directory domain. That's because you don't use Veeam Backup & Replication - many people (not all) do exactly this.
And yes, we know. We typically don’t write about post-auth vulnerabilities. We decided to make an exception, though, because we are talking about a critical piece of software where, bluntly - the authentication requirement is fairly weak.
Imagine that any employee of your 50 000 people organization can get SYSTEM on your backup server. Kind of scary, right? Especially when you think about those threat actors that seemingly and magically appear to get shellz on your endpoints.
Author’s note: This research would never happen if not for my colleague Sina. He insisted that I should have a look at the Veeam deserialization mechanism, and I would have never done this if not him. He has also provided me all the knowledge needed for the exploitation, thus I only needed to focus on an easy stuff - gadget discovery.
Let’s roll!

CVE-2024-40711 Recap
CVE-2024-40711 is fully detailed in our previous blog post. However, we will provide you with a very brief recap here.
Veeam Backup & Replication exposes the .NET Remoting Channel, which as you may know allows you to reach some internal deserialization capabilities based on BinaryFormatter
.
Veeam introduced a custom formatter, which protects against an unsafe deserialization through a whitelist-like mechanism. This is good! Issues appear when the whitelist is too broad though.
Of note though, was the Veeam.Backup.Model.CDbCryptoKeyInfo
class - one of the classes Veeam allows for deserialization.
This class is Serializable
and its magic constructor can be reached through the .NET Remoting deserialization:
namespace Veeam.Backup.Model
{
[Serializable]
public class CDbCryptoKeyInfo : ISerializable, IConcurentTracking, IEquatable<CDbCryptoKeyInfo>, ILoggable, IMetaRecoveryKeyInfo, IMetaCryptoKey, IMetaEntity, IMetaElement, IMetaVisitable
{
protected CDbCryptoKeyInfo(SerializationInfo info, StreamingContext context)
{
CProxyBinaryFormatter cproxyBinaryFormatter = CProxyBinaryFormatter.CreateWithRestrictedBinder();
this.Id = (Guid)info.GetValue("Id", typeof(Guid));
byte[] value = (byte[])info.GetValue("KeySetId", typeof(byte[]));
this.KeySetId = new CKeySetId(value);
this.KeyType = (EDbCryptoKeyType)((int)info.GetValue("KeyType", typeof(int)));
this.EncryptedKeyValue = Convert.FromBase64String(info.GetString("DecryptedKeyValue"));
this.Hint = info.GetString("Hint");
this.ModificationDateUtc = info.GetDateTime("ModificationDateUtc").SpecifyDateTimeUtc();
this.CryptoAlg = (ECryptoAlg)info.GetInt32("CryptoAlg");
this._repairRecs = cproxyBinaryFormatter.DeserializeCustom<CRepairRec>((string[])info.GetValue("RepairRecs", typeof(string[]))).ToList<CRepairRec>(); // [1]
this.Version = info.GetInt64("Version");
this.BackupId = (Guid)info.GetValue("BackupId", typeof(Guid));
this.IsImported = info.GetBoolean("IsImported");
}
//...
//...
}
}
At [1]
, it will call the CProxyBinaryFormatter.DeserializeCustom
method on the attacker-controlled input, which will eventually lead us to the Veeam.Backup.Core.CProxyBinaryFormatter.Deserialize<T>
method:
public static T Deserialize<T>(string input)
{
T result;
try
{
byte[] serializedType = Convert.FromBase64String(input);
BinaryFormatter deserializer = new BinaryFormatter
{
Binder = new RestrictedSerializationBinder(false, RestrictedSerializationBinder.Modes.FilterByBlacklist) // [1]
};
result = CProxyBinaryFormatter.BinaryDeserializeObject<T>(serializedType, deserializer);
}
catch (Exception ex)
{
Log.Exception(ex, "Binary deserialization failed", Array.Empty<object>());
throw;
}
return result;
}
At [1]
, the code defines the RestrictedSerializationBinder
for the deserialization process. This binder is responsible for verifying the target deserialization type. Unfortunately, of course, it is based on a blacklist. It means that we can:
- Deserialize the whitelisted
CDbCryptoKeyInfo
class. - Reach the internal deserialization mechanism, which is controlled by a blacklist.
This is a very nice chain of gadgets.
Nevertheless, we still need to verify the blacklist and see if it misses some commonly known deserialization gadgets. This list is defined in the Veeam.Backup.Common.Sources.System.IO.BinaryFormatter.blacklist.txt
file, which is attached to the Veeam.Backup.Common.dll
as a resource.
This list originally missed the System.Runtime.Remoting.ObjRef
gadget, which is publicly known and can be used to achieve the RCE.
Do you remember our intro points about the blacklist drawbacks? ObjRef
is a relatively new gadget discovered Markus Wulftange and there is a relatively huge chance that this gadget is younger than the Veeam deserialization blacklist.
As we have poked fun at - even if your blacklist is the most accurate list of all the lists in the land, it’s very hard to maintain it.
Regardless, here is a screenshot from our previous blog post, where you can see the difference in the blacklist between different Veeam versions.

As expected, the patch was to extend the deserialization blacklist with the ObjRef
gadget.
Luckily, and as we've alluded to, this is the last deserialization gadget to exist and ever be found, and so we can consider this matter resolved.
The truth is as usual, more brutal. If you're familiar with .NET BinaryFormatter
deserialization, you may quickly notice that this list still misses some known gadgets.
Veeam KB 4693 Patches
If you're reading security advisories and RFC specs before you sleep, and you stumble upon the Veeam KB 4693 advisory, you'll quickly notice CVE-2024-42455 - a vulnerability previously discovered by watchTowr Labs member Sina.

Sina, in his previous work, identified gadgets that could be used maliciously (for local file deletion or NTLM Relaying, as an example) but were not in the blacklist.
As a result, Veeam extended the blacklist again:
System.CodeDom.Compiler.TempFileCollection
System.IO.DirectoryInfo
This list is getting better and better, and soon it will be extra perfect!
But, can we actually find something more there?
Privileges Required to Reach Deserialization
Before we proceed with the deserialization-based vulnerabilities, let’s verify the privileges required to exploit the Veeam .NET Remoting channel.
This is a part that introduces some confusion. When you look at the Veeam advisories for .NET Remoting based vulnerabilities, you find the following statement:
A vulnerability that allows an authenticated user with a role assigned in the Users and Roles settings on the backup server...
This is quite a confusing description, and we are not entirely sure who should be able to access this attack surface. Instead of speculating, we will just show you the real state of things.
We are exploiting the Veeam Mount Service
and it seems that its authorization checks are implemented in the Veeam.Backup.MountServiceLib.CMountServiceAccessChecker.HasAccess
method:
public bool HasAccess(IIdentity identity, Permissions permission)
{
WindowsIdentity windowsIdentity = identity as WindowsIdentity;
if (windowsIdentity == null)
{
Log.Error("Unknown identity: " + identity.Name + ".", Array.Empty<object>());
return false;
}
if (new WindowsPrincipal(windowsIdentity).IsInRole(WindowsBuiltInRole.User)) // [1]
{
Log.Message(LogLevels.HighDetailed, identity.Name + " is in Users group.", Array.Empty<object>());
return true;
}
return CGenericAccessChecker.IsInBuiltinAdministrator(windowsIdentity) || CGenericAccessChecker.IsInBuiltinAdministrator(windowsIdentity) || CGenericAccessChecker.IsInBuiltinAdministratorWithUAC(windowsIdentity);
}
At [1]
, the code verifies if the user belongs to the WindowsBuiltInRole.User
group. If yes, we will pass the check.
When a machine joins the active directory, the Domain Users
group is added to the Windows hosts' local Users
group.
As long as you don’t have a hardened AD configuration (which doesn't extend Users
with domain users), the Veeam .NET Remoting deserialization sink can be accessed by any domain user.
The following screenshot presents our debugging session, where we can see that we have passed the check with the lab\chudy
user:

In the second screenshot, we can see that this user belongs to Domain Users group only.

By the magic of computers and code - we can confirm that all of these vulnerabilities are exploitable by any Domain User. Of course, as long as you have joined your backup server to your AD domain.
Luckily, no one ever does that.

WT-2025-0014 RCE: xmlFrameworkDs gadget
Once we figured out how to reach the deserialization sink based on the blacklist, this game becomes quite simple. Put simply - you only need to find a deserialization gadget which is not blacklisted and leads to some potentially malicious impact.
As we have already stated, abusable classes can be found not only in the .NET Framework, but also typically in the target product's codebase.
Luckily, Veeam Backup & Replication has a huge codebase and contains dozens of massive DLLs. On the other hand, because we are targeting BinaryFormatter
, it strongly narrows our gadget-searching capabilities. This is because BinaryFormatter
can only deserialize classes that fulfil specific conditions, like:
Serializable
attribute must be set for a class.- Magic deserialization methods need to be defined for a class (like the
SerializationInfo
based constructor).
Nevertheless, we decided to take a look around, and we were quickly led to the Veeam.Backup.EsxManager.xmlFrameworkDs
class:
namespace Veeam.Backup.EsxManager
{
[DesignerCategory("code")]
[ToolboxItem(true)]
[XmlSchemaProvider("GetTypedDataSetSchema")]
[XmlRoot("xmlFrameworkDs")]
[HelpKeyword("vs.data.DataSet")]
[Serializable]
public class xmlFrameworkDs : DataSet // [1]
If you have ever read .NET-based deserialization research or used ysoserial.net, you will notice the red flag right away.
At [1]
, you can see that xmlFrameworkDs
extends DataSet
. This is a very common and popular RCE gadget. When you're able to deserialize DataSet
, you get insta-RCE capabilities.
Now, let’s have a quick look at xmlFrameworkDs
magic constructor:
protected xmlFrameworkDs(SerializationInfo info, StreamingContext context) : base(info, context, false) // [1]
{
if (base.IsBinarySerialized(info, context))
{
this.InitVars(false);
CollectionChangeEventHandler value = new CollectionChangeEventHandler(this.SchemaChanged);
this.Tables.CollectionChanged += value;
this.Relations.CollectionChanged += value;
return;
}
//...
}
Here, we can see that xmlFrameworkDs
will call its parent's constructor at [1]
. As its parent is DataSet
, we get an easy RCE here. We just need to modify the DataSet
gadget and accordingly modify the type/assembly names.
In order to exploit this vulnerability, inspired readers and keyboard bashes can likely implement some modifications to Sina’s CVE-2024-40711 PoC.
Firstly, you can replace ObjRefGenerator
with DataSetTypeSpoofGenerator
in the VeeamGenerator.cs
:
//ObjRefGenerator gen = new ObjRefGenerator();
DataSetTypeSpoofGenerator gen = new DataSetTypeSpoofGenerator();
In the DataSetTypeSpoofGenerator.cs
, you can modify the AssemblyName
and FullTypeName
members:
//info.AssemblyName = "mscorlib";
//info.FullTypeName = typeof(System.Data.DataSet).AssemblyQualifiedName;
info.AssemblyName = "Veeam.Backup.EsxManager, Version=12.3.0.0, Culture=neutral, PublicKeyToken=bfd684de2276783a";
info.FullTypeName = "Veeam.Backup.EsxManager.xmlFrameworkDs";
These vague hints should give you a rough idea on how to flawlessly exploit this vulnerability.
WT-2025-0015 RCE: BackupSummary gadget
The second deserialization gadget is Veeam.Backup.Core.BackupSummary
. Let’s have a look at the class definition:
namespace Veeam.Backup.Core
{
[DesignerCategory("code")]
[ToolboxItem(true)]
[XmlSchemaProvider("GetTypedDataSetSchema")]
[XmlRoot("BackupSummary")]
[HelpKeyword("vs.data.DataSet")]
[Serializable]
public class BackupSummary : DataSet // [1]
And you know where this is going, right? There is a second class in the Veeam codebase, which also extends the DataSet
. We can also use this class to achieve the RCE, just like this. There’s nothing more to add here.
No Way, They Did It Again
"Give us the patches, tell us about them" - we get it; nobody can wait with this amount of suspense to see what Veeam has done this time. Here it is:

Surprise surprise! We have added extra perfection to the perfected list!
Who could have possibly predicted this?

Having all the necessary means and tools, you can go on your own quest and try to bypass this perfected perfect blacklist all on your own!
Given the size of the Veeam codebase, we wouldn’t be surprised if other researchers now find numerous further feasible deserialization gadgets.
As a side note: It will never make sense, and we do not agree, that a single CVE is sensible here given multiple avenues to exploit this perfect list of blacklisted classes. If you only add thexmlFrameworkDs
to the blacklist, you can still useBackupSummary
to get the RCE. We’re sure you get the point.
Please just do this properly.
It is hard for us to be positive about this, given the criticality of the solution, combined with the well-known and trodden ground of this solution being targeted by ransomware gangs.
We get chastised for not following someone else's random definition of responsible disclosure, but where is the accountability for vendors who update a text file every time their solution gets popped?
Vulnerable Versions
According to the Veeam KB4724 advisory, vulnerabilities exist in “Veeam Backup & Replication 12.3.0.310 and all earlier version 12 builds.”
CVE-2025-23120 was assigned to these vulnerabilities.

If you have not patched your Veeam server and it is joined to your AD domain, you should probably take this seriously.
Summary
That would be all for the recent Veeam Backup & Replication RCE vulnerabilities. We hope that we have provided yet another proof that protection of deserialization sinks with a blacklist should be illegal (this is not a serious comment, please).
No matter how perfect, or perfecteder and state-of-the-art your list is, somebody will eventually find a way to abuse it.
Timeline
Date | Detail |
5th February 2025 | Vulnerabilities discovered and reported to the vendor |
5th February 2025 | watchTowr hunts across client attack surfaces for impact |
10th February 2025 | Vendor acknowledges the receipt of vulnerabilities details |
5th March 2025 | Vendor confirms the vulnerabilities and says the fix had been developed |
19th March 2025 | Patch released and CVE-2025-23120 assigned |
At watchTowr, we passionately believe that continuous security testing is the future and that rapid reaction to emerging threats single-handedly prevents inevitable breaches.
With the watchTowr Platform, we deliver this capability to our clients every single day - it is our job to understand how emerging threats, vulnerabilities, and TTPs could impact their organizations, with precision.
If you'd like to learn more about the watchTowr Platform, our Attack Surface Management and Continuous Automated Red Teaming solution, please get in touch.