★且听安全★-点关注,不迷路!
★漏洞空间站★-优质漏洞资源和小伙伴聚集地!
**漏洞信息
**
使用 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 不安全的类型绑定
大家更加关注有实际影响力的漏洞,但是我觉得对想学习漏洞挖掘和代码审计的小伙伴而言,分析基础框架的通用性漏洞更有价值。
**SafeBinaryFormatter
**
在 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` 的处理和检查逻辑更加复杂,利用前面的方法无法绕过检查。
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全及文章作者不为此承担任何责任。
★且听安全★-点关注,不迷路!****
★漏洞空间站★-优质漏洞资源和小伙伴聚集地!