作者:且听安全
原文链接: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 如下:

抛出异常将中断后续处理流程,导致反序列化绑定的 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/