CVE-2024-40711&CVE-2025-23120 Veeam Backup .NET 二次反序列化触发及绕过分析

CVE-2024-40711&CVE-2025-23120 Veeam Backup .NET 二次反序列化触发及绕过分析

原创 KCyber 自在安全 2025-03-30 08:00

漏洞概述

Veeam Backup & Replication 通报的 
CVE-2025-23120 是 
CVE-2024-40711 
的延续,两者均为 .NET 反序列化漏洞,都包括两次反序列化过程,分别是白名单绕过的 Remoting 反序列化以及黑名单绕过的 
BinaryFormatter 反序列化,漏洞触发过程比较复杂。

Remoting服务分析

Veeam 定义了很多 .NET Remoting 服务,比如:

6170/tcp  open  remoting             MS .NET Remoting services
9392/tcp  open  remoting             MS .NET Remoting services
9396/tcp  open  remoting             MS .NET Remoting services
9501/tcp  open  remoting             MS .NET Remoting services
9509/tcp  open  remoting             MS .NET Remoting services
10003/tcp open  remoting             MS .NET Remoting services

梳理端口对应的进程服务如下:

6170  ->  Veeam.Backup.MountService.exe
9392  ->  Veeam.Backup.Service.exe 
9396  ->  Veeam.Backup.UIServer.exe 
9501  ->  Veeam.Backup.BrokerService.exe 
9509  ->  Veeam.Backup.Cdp.Service.exe 
10003 ->  Veeam.Backup.CloudService.exe 
...

.NET Remoting 与 Java RMI 服务类似,是微软 .NET Framework 中 RPC 的一种具体实现方案。
.NET Remoting 可以使用 http/tcp/ipc 三种协议进行传输。默认状态下(除非在实例化时指定 _sinkProvider 属性),服务器将通过调用 CreateDefaultServerProviderChain 函数来定义 IServerChannelSinkProvider 对象。
对于 .NET Remoting 反序列化,如果有时间我将单独做一次分享,这里不过多赘述了。 

以 Veeam.Backup.MountService.exe 为例,启动的服务端口为 6170 ,分析 TcpServerChannel 关键字,找到了其中一个实现类 CSrvTcpChannelRegistration :

CSrvTcpChannelRegistration 构造函数中完成了 TcpServerChannel 注册,并通过 CSrvTcpChannelRegistration.GetSinkProvider 函数实例化了 IServerChannelSinkProvider 类型:

.NET Remoting 中的 MountService 对应服务端口正好为 6170 (cmountServiceRegistryOptions.Port):

Veeam.Backup.MountService.Program.StartProcess 函数中实例化了 CMountServiceServer 对象,然后通过 StartLeasing 函数注册该 .NET Remoting 服务,终结点 URI 取值为 PermanentSessionService :

CVE-2024-40711

1. 
CBinaryServerFormatterSinkProvider 反序列化

回到 CSrvTcpChannelRegistration 构造函数,Veeam 自定义了 3 种 IServerChannelSinkProvider ,分别是:

CBinaryServerFormatterSinkProvider
CActivityMonitorServerSinkProvider
CImpersonationServerSinkProvider

CSrvTcpChannelRegistration.GetSinkProvider 根据输入参数的不同,实例化不同类型的 IServerChannelSinkProvider 对象:

重点关注其中的 CBinaryServerFormatterSinkProvider ,对应的 Sink 为 CBinaryServerFormatterSink 类型,反序列化操作通过 ProcessMessage 来完成,该函数与 .NET Framework 自带的 BinaryServerFormatterSink 类似,反序列化函数为 DeserializeBinaryRequestMessage :

函数中不仅限定了 TypeFilterLevel 为 Low ,还针对 BinaryFormatter 反序列化类型调用 RestrictedSerializationBinder 进行黑白名单检查,这里 mode 取值为 FilterByWhitelist ,对应白名单检查:

2. 
二次反序列化实现黑白名单Bypass

白名单定义在 CWhitelist 类中,除了加入常见的类型外,其他的白名单定义在 whitelist.txt 文件中:

既然启用了白名单,那么只能尝试在合法的类型中寻找可利用对象。同时 Veeam 中还定义了黑名单(定义在 CBlacklist 类中,黑名单位于 blacklist.txt),只不过在 CBinaryServerFormatterSinkProvider 中没有用到黑名单:

大部分已经曝光的 gadget 被加入了黑名单,但是也有一些比较新的链被遗漏了,比如 ObjRef 。那么哪些地方用到了黑名单呢?通过搜索找到了 CProxyBinaryFormatter 类,该类中的 Deserialize 函数在反序列化时采用的是黑名单方式:

很容易想到可以尝试通过二次反序列化来进行绕过。重点关注位于白名单且调用了 CProxyBinaryFormatter.Deserialize 函数的对象,因为这样的对象既可以绕过 CBinaryServerFormatterSinkProvider 白名单限制,又可以结合 ObjRef 绕过后面的黑名单。分析后发现下面 3 个类满足要求:

CEpContainerSaveInfo  
CDbCryptoKeyInfo  
CUserRequestSpecificationConfigurationBackup

关注其中的 CDbCryptoKeyInfo 类,该类继承了 ISerializable 接口, BinaryFormatter 反序列化时会自动调用构造函数,在序列化时将调用 GetObjectData 函数:

这里通过 .NET 泛型类型定义的方式将反序列化对象限定为 CRepairRec 类,注意到 CRepairRec 类中存在一个名为 EncryptedKeys 的字节数组,正好可以将反序列化的 gadget 放入其中,这样在 CBinaryServerFormatterSinkProvider 反序列化检查白名单时,CDbCryptoKeyInfo 对象可以绕过,同时在其构造函数中会触发 CRepairRec 对象的二次反序列化,结合 ObjRef 链可以绕过黑名单:

综上,
通过二次反序列化可以绕过了黑白名单限制。

  1. 利用过程

完整利用过程如下:

最终成功触发 RCE :

  1. 修复方式

将 ObjRef 加入了黑名单:

但是第一次绕过白名单触发 .NET Remoting 反序列化的过程没有修复,这也为后续的绕过提供了可能性。

CVE-2025-23120

修复补丁只将 
ObjRef  加入了黑名单,如果能够找到新的 gadget ,那么就能够再次 RCE。BinaryFormatter 存在很多公开的 gadget ,比如 TextFormattingRunProperties 或者 DataSet 等等,但是这些 gadget 早就被加入了黑名单。可以尝试在 Veem 自生类中寻找继承了这些类的子类。搜索找到 3 个继承 DataSet 的类: xmlFrameworkDs 、 BackupSummary 和 CDataSetSurrogate  ,
并且这些子类均不在黑名单中:

xmlFrameworkDs 和 BackupSummary 类构造函数会调用父类 DataSet 构造函数,但是 CDataSetSurrogate 不会,因此 xmlFrameworkDs 和 BackupSummary 作为 gadget 也可以触发 RCE :

Veeam 黑名单检测主要对 assemblyName 和 typeName 进行检测,
可以尝试在 ysoserial.net 中新增生成 xmlFrameworkDs 或者 BackupSummary 类的 gadget 生成代码。重写 GetObjectData 函数,将 assemblyName 和 FullTypeName 设置为合法值即可

触发二次反序列化的主要调用栈如下:

成功实现 RCE :

针对 BinaryFormatter 反序列化,可以搜索那些继承了经典 gadget 的子类,如果其构造函数也调用了父类构造函数,那么就有可能用来绕过黑名单限制。

由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,公众号及文章作者不为此承担任何责任。