技术速递|从 .NET 9 中移除 BinaryFormatter

作者:Immo Landwerth
排版:Alan Wang

从 .NET 9 开始,我们不再在运行时中包含 BinaryFormatter 的实现(.NET Framework 保持不变)。API 仍然存在,但无论项目类型是什么,它们的实现始终会抛出异常。因此,设置现有的向下兼容性标志已不足以使用 BinaryFormatter。

在这篇博文中,我将解释为什么做出这一更改以及您可以采取哪些选项。

TL;DR:我该怎么办?

您有两个应对 BinaryFormatter 实现移除的选项:

  1. 迁离 BinaryFormatter。我们强烈建议您考虑停止使用 BinaryFormatter,因为它存在安全风险。BinaryFormatter 迁移指南提供了多种替代方案。

  2. 继续使用 BinaryFormatter。如果您需要在 .NET 9 中继续使用 BinaryFormatter,则需要依赖不受支持的 System.Runtime.Serialization.Formatters NuGet 包,该包恢复了不安全的旧版功能并替换了抛出异常实现。

注意事项

请注意,.NET Framework 不受此更改的影响,并继续包含 BinaryFormatter 的实现。但是,出于同样的原因,我们仍然强烈建议停止使用 .NET Framework 中的 BinaryFormatter。

使用 BinaryFormatter 有什么风险?

任何允许输入携带有关要创建对象信息的反序列化器(无论是二进制还是文本),都是一个潜在的安全问题。有一个常见弱点枚举(CWE)描述了这个问题:CWE-502“不受信任数据的反序列化”。2002 年 .NET Framework 的初始版本中包含的 BinaryFormatter 就是这样一个反序列化器。我们在 BinaryFormatter 安全指南中也讨论了这一点。

我们为什么要移除 BinaryFormatter

我们坚信 .NET 应该让客户能够轻松地做正确的事情,并尽量避免或杜绝错误的操作。我们通常将此称为“成功的陷阱”。

发布一种被广泛认为是不安全的技术与这一目标背道而驰。与此同时,我们也有责任确保客户能够支持并推进他们现有代码的发展。我们不能只是简单地从 .NET 版本中删除广泛使用的组件,即使提前很久就进行了通知。我们还需要一个迁移计划和临时解决方案。

这次移除并非突然发生。由于我们已经知道使用 BinaryFormatter 时,会存在风险,所以我们将其从 .NET Core 1.0 中排除。但因为缺乏明确的迁移路径来使用更安全的替代方案,并且客户依旧存在需求,我们在 .NET Core 2.0 中重新引入了 BinaryFormatter。

从那时起,我们就一直在努力移除 BinaryFormatter,在多种项目类型中慢慢将其默认禁用,但如果使用者仍然需要向下兼容,则允许他们通过可选标记使用:

在 .NET 9 中,我们移除了 BinaryFormatter 上所有剩余的内置依赖项,并用一个始终抛出异常的实现替换了它。

前进的方向

新代码不应该依赖于 BinaryFormatter。对于现有代码,您应该首先研究 BinaryFormatter 的替代方案。如果您不控制序列化程序而只执行反序列化,则可以考虑只读取 BinaryFormatter 有效负载,而不执行任何反序列化。如果以上方法均不适用于您,您可以通过依赖(不受支持的)兼容包来恢复实现。

我将在下文中详细探讨这些选项。

迁移替代

首先,您应该调查是否可以用其他序列化程序替换 BinaryFormatter。我们有四条建议:

由于 DataContractSerializer 遵循与 BinaryFormatter 相同的属性和接口(即 [Serializable] 和 ISerializable),因此它可能是最容易迁移的。如果您的迁移目标是采用现代、高性能的序列化器,或需要更好的跨平台兼容性,其他选项也值得考虑。

读取 BinaryFormatter 的数据

如果您的代码不控制序列化而只控制反序列化,请使用新的 NrbfDecoder 读取 BinaryFormatter 的数据。这允许您在没有任何反序列化的情况下读取编码数据。这相当于使用不带反序列化的 JSON/XML 读取器:

using System.Formats.Nrbf;
void Read(Stream payload)
{
    SerializationRecord rootObject = NrbfDecoder.Decode(payload);
    if (rootObject is PrimitiveTypeRecord primitiveRecord)
    {
        Console.WriteLine($"It was a primitive value: '{primitiveRecord.Value}'");
    }
    else if (rootObject is ClassRecord classRecord)
    {
        Console.WriteLine($"It was a class record of '{classRecord.TypeName.AssemblyQualifiedName}' type name.");
    }
    else if (rootObject is SZArrayRecord<byte> arrayOfBytes)
    {
        Console.WriteLine($"It was an array of `{arrayOfBytes.Length}`-many bytes.");
    }
}

有关更多详细信息,请查看 Nrbf 文档

BinaryFormatter 兼容包

如果您已经探索过这些选项并确定无法迁离 BinaryFormatter,那么您还可以安装不受支持的 System.Runtime.Serialization.Formatters NuGet 包并将兼容性开关设置为 true:

<PropertyGroup>
  <TargetFramework>net9.0</TargetFramework>
  <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="System.Runtime.Serialization.Formatters" Version="9.0.0" />
</ItemGroup>

该包将 BinaryFormatter 的内置实现替换为可正常运行的实现,同时也带回了漏洞和风险。如果您迫不及待地想要迁移到 .NET 9,并且尚未替换 BinaryFormatter,那么它可作为临时解决方案。

由于 BinaryFormatter API 仍然存在,并且此包仅替换了内置实现,因此您只需从应用程序项目中引用它即可。针对 BinaryFormatter 编译的现有代码将继续工作。

注意事项

该兼容包不受支持且不安全。我们强烈建议不要依赖此包,而是尽快迁移离开 BinaryFormatter。

总结

自 .NET Core 发布以来,我们一直在逐步淘汰 BinaryFormatter,因为它存在安全风险

从 .NET 9 开始,我们不再在运行时中提供该实现。我们建议您从 BinaryFormatter 迁离。如果您无法做到这一点,您可以尝试在不进行反序列化的情况下读取二进制有效负载,或者您可以依赖于不受支持的兼容性包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值