.NET 反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定
source link: https://paper.seebug.org/1927/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
作者:且听安全
原文链接:https://mp.weixin.qq.com/s/F2jFMkmN3K9yn_uuICStuA
很多 .NET 应用程序在修复 BinaryFormatter
、 SoapFormatter
、LosFormatter
、 NetDataContractSerializer
、ObjectStateFormatter
等反序列化漏洞时,喜欢通过自定义 SerializationBinder
来限定类型,从而达到缓解反序列化攻击的目的。历史上很多 .NET 反序列化漏洞都采用了这种方法,但是我们查看微软官方的警告说明:
使用 SerializationBinder
无法完全修复反序列化漏洞隐患。最近看到老外发了一篇相关文章,感觉很有价值,自己也深入研究总结了两种不安全的 SerializationBinder
限定方式,下面分享给大家。
SerializationBinder 绑定限制
常见修复方式就是对 BinaryFormatter
反序列化过程绑定 Binder
对象,通过 SerializationBinder
来检查反序列化类型。构建如下 demo
。
反序列化操作如下:
using (var fileStream = new FileStream(file, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
fileStream.Position = 0;
formatter.Binder=new SafeDeserializationBinder();
formatter.Deserialize(fileStream);
}
自定义 SafeDeserializationBinder
继承于 SerializationBinder
,通过黑名单机制进行检查,当发现存在恶意类型时,比如 System.Data.DataSet
,将阻断反序列化过程:
public class SafeDeserializationBinder : SerializationBinder
{
List<String> blackTypeName = new List<string> { };
private void _AddBlackList()
{
blackTypeName.Add("System.Data.DataSet");
}
public override Type BindToType(string assemblyName, string typeName)
{
this._AddBlackList();
foreach (var t in blackTypeName)
{
if (typeName.Equals(t))
{
//todo
}
}
return Type.GetType(typeName);
}
}
Bypass 1 :无效的 null 返回值
大家很容易想到,当检测到反序列化黑名单,直接返回 null
:
这样真的可以阻断反序列化漏洞吗?我们可以进行测试。利用 YSoSerial.Net
特定生成 System.Data.DataSet
的反序列化载荷:
ysoserial.exe -o raw -f BinaryFormatter -g DataSet -c calc >payload.txt
确实返回了 null
,但是发现最终还是执行了反序列化操作并触发了 RCE:
为什么呢?下面调试分析一下原因。BinaryFormatter
反序列化时将调用 ObjectReader#Bind
来获取 Type
类型:
首先调用自定义的 SafeDeserializationBinder#BindToType
,当返回 null
时,函数并没有直接结束,而是继续调用 FastBindToType
来获取 Type
对象:
首先尝试从 typecache
缓存中提取,程序首次调用获取不到值,继续判断 bSimpleAssembly
的取值(默认始终为 true
),进而尝试调用 GetSimplyNamedTypeFromAssembly
:
通过 FormatterServices#GetTypeFromAssembly
最终取到了 type
的值:
所以尝试在 SerializationBinder
加载恶意 Type
时通过返回 null
是无法阻断反序列化漏洞的。比如 Exchange CVE-2022-23277 就是由于在遇到黑名单时最终返回 null
从而导致被绕过。要想 SerializationBinder
有效,正确的做法是抛出异常,修改 demo
如下:
抛出异常将中断后续处理流程,导致反序列化绑定的 Type
最终确定为 null
,从而无法触发反序列化漏洞。
Bypass 2 :抛出异常真的安全吗?
上面通过抛出异常的方式真的能够完全修复漏洞吗?答案是否定的。我们思考下既然反序列化操作可以通过 BindToType
检查 Type
和 Assembly
,那么在生成序列化载荷时,就可能可以自定义 Type
和 Assembly
,查看 YSoSerial.Net
生成 System.Data.DataSet
的反序列化载荷的代码段 ( /Generators/DataSetGenerator.cs
):
我们可以手动去修改 Type
的赋值过程,确保让其不位于黑名单之中,比如:
重新编译生成 YSoSerial.Net
,并再次生成新的 DataSet
反序列化载荷:
此时 typeName
并不在黑名单之中,所以不会抛出异常,但是却成功返回了正确的 Type
类型,从而绕过检查进而实现了 RCE :
比如 DevExpress CVE-2022-28684 反序列化漏洞就是通过类似上面这种方式实现 Bypass 的。
通过 SerializationBinder
绑定 Type
类型来缓解反序列化漏洞,无论是直接返回 null
还是抛出异常,都存在被绕过的风险,最好的修复方式其实微软官方已经给出了答案,那就是不要使用 BinaryFormatter
这类反序列化类:
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1927/
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK