0

.NET 反序列化漏洞之绕过 SerializationBinder 不安全的类型绑定

 1 year ago
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 应用程序在修复 BinaryFormatterSoapFormatterLosFormatterNetDataContractSerializerObjectStateFormatter 等反序列化漏洞时,喜欢通过自定义 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 如下:

bc330165-4f3d-4436-a3ed-1a7184515523.png-w331s

抛出异常将中断后续处理流程,导致反序列化绑定的 Type 最终确定为 null ,从而无法触发反序列化漏洞。

Bypass 2 :抛出异常真的安全吗?

上面通过抛出异常的方式真的能够完全修复漏洞吗?答案是否定的。我们思考下既然反序列化操作可以通过 BindToType 检查 TypeAssembly ,那么在生成序列化载荷时,就可能可以自定义 TypeAssembly ,查看 YSoSerial.Net 生成 System.Data.DataSet 的反序列化载荷的代码段 ( /Generators/DataSetGenerator.cs ):

图片

我们可以手动去修改 Type 的赋值过程,确保让其不位于黑名单之中,比如:

图片

重新编译生成 YSoSerial.Net ,并再次生成新的 DataSet 反序列化载荷:

图片

此时 typeName 并不在黑名单之中,所以不会抛出异常,但是却成功返回了正确的 Type 类型,从而绕过检查进而实现了 RCE :

图片

比如 DevExpress CVE-2022-28684 反序列化漏洞就是通过类似上面这种方式实现 Bypass 的。

通过 SerializationBinder 绑定 Type 类型来缓解反序列化漏洞,无论是直接返回 null 还是抛出异常,都存在被绕过的风险,最好的修复方式其实微软官方已经给出了答案,那就是不要使用 BinaryFormatter 这类反序列化类:

图片

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1927/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK