★且听安全★-点关注,不迷路!
★漏洞空间站★-优质漏洞资源和小伙伴聚集地!
使用 C# 做过界面开发的小伙伴应该对 DevExpress 框架库非常熟悉,它是一套基于 .Net 的 UI 控件库,也是目前 .Net 下最为强大的完整的一套 UI 控件库,主要集成了 WinForm 和 WebForm 下的一些常用控件和 UI 元素。
在分析绕过 `SerializationBinder` 不安全的类型绑定过程中,关注到了 DevExpress CVE-2022-28684 反序列化漏洞。ZDI 通报如下:
影响版本如下:
DevExpress: before 18.1.18, 18.2.17, 19.1.15, 19.2.14, 20.1.15, 20.2.11, 21.1.9, 21.2.7, 22.1.1
在阅读文章开始前,建议小伙伴们先移步公众号的前一篇文章:
.NET反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定
QCyber,公众号:且听安全.NET反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定
大家更加关注有实际影响力的漏洞,但是我觉得对想学习漏洞挖掘和代码审计的小伙伴而言,分析基础框架的通用性漏洞更有价值。
在 DevExpress 提供了一个 `SafeBinaryFormatter` 的安全反序列化静态类:
对外提供了三个反序列化函数接口:
我们可以尝试调用 `Deserialize` 来加载 `YSoSerial.Net` 生成的反序列化载荷:
using (var fileStream = new FileStream(file, FileMode.Open))
{
SafeBinaryFormatter.Deserialize(fileStream);
}
抛出异常,无法像直接使用 .NET 官方提供的 `BinaryFormatter` 那样实现 RCE :
`SafeBinaryFormatter#Deserialize` 反序列化处理过程:
跟进 `DeserializeWithSecurityExceptionUnwrap` 函数:
通过 `Instance` 只读属性获取 `BinaryFormatter` 对象,然后调用 `BinaryFormatter#Deserialize` 执行反序列化操作。我们重点看一下 `BinaryFormatter` 的定义过程:
到这里大家就明白了失败的原因。`SafeBinaryFormatter` 自定义的 `BinaryFormatter` 对象在初始化时会绑定一个继承于 `SerializationBinder` 的 `DXSerializationBinder` 对象,限制反序列化操作的代码位于 `DXSerializationBinder#BindToType` 函数中:
跟进分析 `DXSerializationBinder#BindToType` 函数:
进入 `SafeSerializationBinder#Ensure` :
首先利用 `UnsafeType#Match` 进行匹配,匹配成功将调用 `XtraSerializationSecurityTrace.UnsafeType` 断言判断抛出异常。`UnsafeType#Match` 定义如下:
public static bool Match(string assembly, string type)
{
return SafeSerializationBinder.TypeRanges.Match(SafeSerializationBinder.UnsafeTypes.typeRanges, assembly, type) ||
SafeSerializationBinder.TypeRanges.MatchDX(SafeSerializationBinder.UnsafeTypes.dxTypeRanges, assembly, type) ||
SafeSerializationBinder.UnsafeTypes.types.Contains(type) ||
(FrameworkVersions.IsMonoRuntime &&SafeSerializationBinder.UnsafeTypes.wasmTypes.Contains(type));
}
`SafeSerializationBinder.UnsafeTypes.typeRanges` 定义如下:
`SafeSerializationBinder.UnsafeTypes.dxTypeRanges` 定义如下:
`SafeSerializationBinder.UnsafeTypes.types` 定义如下:
private static readonly HashSet<string> types = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
{
"System.DelegateSerializationHolder",
"System.DelegateSerializationHolder+DelegateEntry",
"System.Reflection.MemberInfoSerializationHolder",
"System.UnitySerializationHolder",
"System.Management.Automation.PSObject",
"System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector",
"System.AppDomainSetup",
"System.Exception",
"System.BadImageFormatException",
"System.MissingFieldException",
"System.MissingMemberException",
"System.MissingMethodException",
"System.IO.FileLoadException",
"System.IO.FileNotFoundException",
"System.Security.SecurityException",
"System.Security.Permissions.PublisherIdentityPermissionAttribute",
"System.Security.Permissions.PermissionSetAttribute",
"System.Collections.Generic.ComparisonComparer`1",
"System.Reflection.RuntimeAssembly",
"System.Reflection.DefaultMemberAttribute",
"System.Reflection.Emit.ModuleBuilderData",
"System.Net.FileWebRequest",
"System.Net.HttpWebRequest",
"System.Media.SoundPlayer",
"System.Configuration.ConfigurationException",
"System.Runtime.Versioning.FrameworkName",
"System.Drawing.Design.ToolboxItem",
"System.RuntimeType",
"System.RuntimeTypeHandle",
"System.Resources.ResourceManager",
"System.Reflection.RuntimeConstructorInfo",
"System.Reflection.RuntimeEventInfo",
"System.Reflection.RuntimeFieldInfo",
"System.Reflection.RuntimeMethodInfo",
"System.Reflection.RuntimePropertyInfo",
"System.Reflection.RuntimeParameterInfo",
"System.Reflection.RuntimeModule",
"System.Reflection.RtFieldInfo",
"System.Reflection.MdFieldInfo",
"System.Reflection.ParameterInfo",
"System.Reflection.Pointer",
"System.Reflection.TypeDelegator",
"System.Reflection.CustomAttributeTypedArgument",
"System.Runtime.CompilerServices.RequiredAttributeAttribute",
"System.Runtime.CompilerServices.StateMachineAttribute",
"System.Security.Permissions.ResourcePermissionBase",
"System.ComponentModel.LicenseException",
"System.CodeDom.Compiler.TempFileCollection",
"System.Data.DataSet"
};
包括我们测试使用的 `System.Data.DataSet` 利用链在内的几乎所有 `YSoSerial.Net` 中已知的 Gadget ,调用过程在 `BindToType` 函数中抛出异常,最终导致反序列化操作被阻断。
回顾前面公众号文章《.NET反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定》,我们可以通过修改 `YSoSerial.Net` 代码段 `/Generators/DataSetGenerator.cs` 来通过 `SafeSerializationBinder#Ensure` 检查:
继续往下走,发现 `EnsureAssemblyQualifiedTypeName` 函数会修改 `assemblyName` 和 `typeName` 的值,从而再次调用 `Ensure` 函数时还是会检查失败抛出异常:
跟进 `EnsureAssemblyQualifiedTypeName` 函数:
我们的目标是想让 `EnsureAssemblyQualifiedTypeName` 函数返回 `false` ,这样将可以避开 `Ensure` 函数对 `typeName` 的二次检查。一共有三处确定返回 `false` 的地方,我们可以重点关注第 88 行处的处理过程:
`num2` 变量的取值为 `typeName` 最后一个 `,` 的索引;
代码中会根据尝试截取生成新的 `typeName` ,截取的位置刚好就是 `num2` ;
如果 `typeName` 中包含 `version=` ,将提取 `typeName` 最后一个 `]` 的位置并赋值给 `num4` ,当 `num4 > num2` 时函数将返回 `false`。
因此可以修改 `DataSetGenerator.cs` 代码中的 `GetObjectData` ,直接在后面添加 `,]` 就可以满足上面的条件,同时为了符合 .NET `Type` 全名命名规则,最终修改如下:
重新生成反序列化载荷再次测试:
返回 `false` ,绕过了 `Ensure` 函数对 `typeName` 的二次检查。
回到 `BindToType` 函数继续往下走:
第 466 行 `SafeSerializationBinder.DXSerializationBinder#GetAssembly` 尝试根据 `assemblyName` 获取 `assembly` ,跟进:
第 488 行 `Assembly.Load` 加载不存在的组件名会导致 `BindToType` 函数返回 `null`:
在《.NET反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定》中已经分析,当 `BindToType` 返回 `null` 时,还会继续调用 `FastBindToType` 对象:
因为 `assemblyName` 不存在会抛出异常,获取 `Type` 类型为空,从而导致反序列化操作再次被阻断。这个地方绕过很容易,我们再次修改 `DataSetGenerator.cs` 代码,在 `GetObjectData` 中赋值一个合理的组件名,比如 `System`:
再次测试成功通过 `FastBindToType` 组件检查并返回合法 `Type` :
最终触发 RCE。
漏洞修复主要位于 `EnsureAssemblyQualifiedTypeName` 函数,新增 `AssemblyQualifiedTypeNameParser` 密封类:
对参数 `assemblyName` 和 `typeName` 的处理和检查逻辑更加复杂,利用前面的方法无法绕过检查。
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全及文章作者不为此承担任何责任。
★且听安全★-点关注,不迷路!
★漏洞空间站★-优质漏洞资源和小伙伴聚集地!