binaryformatter在.net 5+中被弃用,因其反序列化机制存在严重安全风险,可能被利用执行远程代码;2. 使用它时必须确保类标记[serializable],通过流进行序列化与反序列化操作,并可借助[nonserialized]控制字段;3. 其主要风险在于反序列化不可信数据时可能触发恶意类型实例化,形成反序列化漏洞;4. 推荐替代方案包括system.text.json、newtonsoft.json、protobuf和messagepack,它们更安全高效;5. 仅在遗留系统或完全可信环境中才应考虑使用binaryformatter;6. 若必须使用,应通过serializationbinder限制类型、校验数据完整性、遵循最小权限原则并尽快迁移到现代方案,以降低风险,但根本解决之道是停止使用。

C#中的
BinaryFormatter,它提供了一种将对象转换为字节流(序列化)以便存储或传输,然后再将字节流恢复为对象(反序列化)的机制。从我的个人经验来看,它曾经是一个方便的工具,尤其在早期.NET框架中,用于将对象状态持久化到文件或在AppDomain之间传递数据。但随着技术发展和安全意识的提升,它现在已经是一个被明确不推荐使用的“遗留”方案了,尤其是在处理来自不可信源的数据时,它的风险远大于便利。
在实际操作中,使用
BinaryFormatter来序列化一个对象,核心在于几个步骤:首先,你得确保你的对象类被标记为可序列化;接着,你需要一个流(比如文件流或内存流)来承载序列化后的字节数据;最后,通过
BinaryFormatter的实例调用相应的方法。
将C#对象通过
BinaryFormatter进行序列化,主要涉及以下几个步骤和注意事项:
1. 标记可序列化类型: 你的类必须使用
[Serializable]特性进行标记。这是
BinaryFormatter识别一个类型是否允许被序列化的前提。
[Serializable]
public class MyDataObject
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
// 这个字段不会被序列化
[NonSerialized]
public string TempInfo;
public MyDataObject(int id, string name)
{
Id = id;
Name = name;
CreatedDate = DateTime.Now;
TempInfo = "这是一个临时信息";
}
public override string ToString()
{
return $"Id: {Id}, Name: {Name}, Created: {CreatedDate}, TempInfo (NonSerialized): {TempInfo}";
}
}2. 序列化对象: 你需要一个
BinaryFormatter的实例和一个
Stream对象。通常会用
FileStream来写入文件,或者
MemoryStream在内存中操作。
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization; // For ISerializable if needed
// 创建一个要序列化的对象实例
MyDataObject originalObject = new MyDataObject(101, "示例数据");
Console.WriteLine($"原始对象: {originalObject}");
// 序列化到文件
string filePath = "mydata.bin";
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, originalObject);
}
Console.WriteLine($"对象已序列化到 {filePath}");
}
catch (SerializationException e)
{
Console.WriteLine($"序列化失败: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"发生未知错误: {e.Message}");
}3. 反序列化对象: 反序列化是将字节流重新转换回对象的过程。同样需要一个
BinaryFormatter实例和从文件或内存中读取的
Stream。
MyDataObject deserializedObject = null;
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
BinaryFormatter formatter = new BinaryFormatter();
// 关键点:反序列化时,你必须非常信任数据的来源!
deserializedObject = (MyDataObject)formatter.Deserialize(fs);
}
Console.WriteLine($"对象已从 {filePath} 反序列化成功。");
Console.WriteLine($"反序列化对象: {deserializedObject}");
// 注意:TempInfo 字段因为 [NonSerialized] 而不会被保留其值,会是其默认值(null)
// 这就是为什么原始对象的 TempInfo 有值,而反序列化后会是 null
if (deserializedObject.TempInfo == null)
{
Console.WriteLine("注意: TempInfo 字段因为被标记为 [NonSerialized] 而没有被序列化/反序列化。");
}
}
catch (SerializationException e)
{
Console.WriteLine($"反序列化失败: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"发生未知错误: {e.Message}");
}4. 控制序列化行为(可选): 如果你需要更精细地控制序列化过程,例如在序列化时排除特定字段,或者在反序列化时进行一些自定义初始化,可以实现
ISerializable接口。这会让你自己编写
GetObjectData和特殊的构造函数来处理序列化和反序列化逻辑。但这通常会增加复杂性,并且在现代应用中,我们有更多推荐的替代方案。
BinaryFormatter
为何在.NET 5+中被弃用?其潜在的安全风险是什么?
BinaryFormatter的弃用并非偶然,而是源于其固有的、难以规避的安全漏洞。核心问题在于,它在反序列化时会尝试实例化并执行字节流中描述的任何类型,这其中就包括了潜在的恶意类型。如果攻击者能够控制输入到
BinaryFormatter的字节流,他们就可以构造一个恶意的“gadget chain”(小工具链),这个链条由一系列看似无害的对象组成,但当它们被
BinaryFormatter按特定顺序实例化并连接起来时,就会触发意想不到的副作用,例如执行任意代码。
这被称为“反序列化漏洞”,它是一个臭名昭著的远程代码执行(RCE)向量。想象一下,你的应用程序从网络接收到一段数据,然后天真地使用
BinaryFormatter去反序列化它。如果这段数据是攻击者精心构造的恶意负载,你的应用程序就会在不知不觉中执行攻击者指定的代码,这可能导致数据泄露、系统破坏甚至完全控制。由于
BinaryFormatter的设计机制,即使不使用
[Serializable]特性,攻击者也可能通过反射等手段绕过一些限制。因此,微软在.NET 5及更高版本中明确将其标记为过时,并强烈建议开发者停止使用它,尤其是在处理任何来自不可信源的数据时。这不仅仅是建议,更是对开发者安全责任的警示。
替代BinaryFormatter
的现代序列化方案有哪些?
鉴于
BinaryFormatter的安全隐患,现代.NET开发中,我们有更多安全且高效的序列化选择。这些替代方案不仅在安全性上更有保障,通常在性能、跨平台兼容性以及易用性上也有显著优势:
System.Text.Json
(推荐):这是.NET Core 3.0及以后版本内置的高性能JSON序列化器。它专注于JSON格式,性能卓越,内存占用低,并且默认是安全的(不会反序列化任意类型)。它非常适合Web API、前后端数据交换以及配置文件的存储。其基于合约(Contract-based)的序列化方式,也提供了很好的控制粒度。Newtonsoft.Json (Json.NET):作为事实上的.NET JSON序列化标准,
Newtonsoft.Json
在System.Text.Json
出现之前几乎是所有.NET项目的首选。它功能强大,灵活度极高,支持各种复杂的序列化场景,包括自定义转换器、LINQ to JSON等。虽然System.Text.Json
在某些基准测试中可能更快,但Newtonsoft.Json
凭借其丰富的功能集和成熟的生态系统,在很多现有项目中依然被广泛使用。Google Protocol Buffers (Protobuf):如果你的应用对性能和数据包大小有极高要求,并且需要跨语言通信,Protobuf是一个非常优秀的二进制序列化方案。它通过定义
.proto
文件来描述数据结构,然后生成对应语言的代码,序列化后的数据非常紧凑,解析速度快。它在微服务、高性能计算和移动应用等领域非常流行。MessagePack for C#:类似于Protobuf,MessagePack也是一种高效的二进制序列化格式。它以其极快的序列化/反序列化速度和紧凑的数据格式而闻名,并且支持Schema-less(无模式)序列化,这在某些场景下提供了更大的灵活性。
System.Xml.Serialization.XmlSerializer
/System.Runtime.Serialization.DataContractSerializer
:这两种是XML序列化器。XmlSerializer
主要用于将对象序列化为符合特定XML Schema定义的XML文档,常见于SOAP Web服务和旧式集成。DataContractSerializer
则更侧重于数据契约(Data Contract)的概念,通常与WCF服务一起使用,它提供了更好的版本容忍性。虽然它们仍然有效,但在新项目中,XML通常不如JSON或二进制格式流行。
选择哪种方案,取决于你的具体需求:是需要人类可读性、跨平台兼容性、极致性能,还是与其他系统互操作。
在什么情况下仍然可能需要使用BinaryFormatter
?以及如何降低其潜在风险?
尽管
BinaryFormatter已被弃用,但在某些特定且受限的场景下,你可能仍然会遇到或不得不使用它。这通常发生在以下情况:
遗留系统集成:你正在维护或与一个历史悠久的.NET应用程序交互,该程序在设计之初就大量依赖
BinaryFormatter
来存储数据(例如,将对象序列化到文件、数据库BLOB字段)或进行进程间通信。在这种情况下,短期内完全重构所有序列化逻辑可能成本过高或不可行。受控且高度信任的环境:在极少数情况下,如果你的应用程序运行在一个完全隔离、数据来源绝对可信的环境中(例如,仅用于应用程序内部的、进程内的数据传递,且数据完全由你自己的代码生成和消费,没有任何外部输入),并且你完全了解其风险,理论上可以使用。但这仍然不推荐,因为即使是内部数据,也可能因程序漏洞导致数据被篡改。
如何降低潜在风险(如果必须使用):
请注意,以下措施并不能完全消除
BinaryFormatter的固有风险,它们只是在特定限制下提供一些缓解。最根本的原则是:绝不反序列化来自不可信源的数据!
-
限制反序列化的类型(
SerializationBinder
):这是最有效的缓解措施之一。你可以自定义一个继承自SerializationBinder
的类,并重写其BindToType
方法。在这个方法中,你可以严格限制允许反序列化的类型列表。如果尝试反序列化的类型不在你的白名单中,就抛出异常。public sealed class WhitelistSerializationBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { // 严格限制只允许反序列化 MyDataObject 类型 // 注意:这里需要完整的类型名称,包括命名空间和程序集信息 if (typeName == "YourNamespace.MyDataObject" && assemblyName.StartsWith("YourAssemblyName,")) { return Type.GetType($"{typeName}, {assemblyName}"); } // 阻止反序列化其他任何类型 throw new SerializationException($"不允许反序列化类型: {typeName}, {assemblyName}"); } } // 使用时: // formatter.Binder = new WhitelistSerializationBinder(); // deserializedObject = (MyDataObject)formatter.Deserialize(fs);这个方法可以有效阻止恶意类型被实例化,但它要求你对所有可能被序列化的类型有清晰的白名单。
数据完整性校验:在反序列化之前,对数据流进行完整性校验,例如通过计算哈希值并与已知安全哈希值进行比对。如果哈希值不匹配,则拒绝反序列化。这可以防止数据在传输或存储过程中被篡改,但无法阻止攻击者构造新的恶意负载。
最小权限原则:运行序列化/反序列化代码的进程,应以尽可能低的权限运行,限制其对文件系统、网络或其他资源的访问能力。即使发生RCE,也能限制其造成的损害。
隔离处理:如果可能,将
BinaryFormatter
的反序列化操作放在一个独立的、沙箱化的进程中。即使该进程被攻破,也难以影响到主应用程序或其他关键系统。尽快迁移:以上所有措施都是权宜之计。长远来看,最根本的解决方案是逐步将所有使用
BinaryFormatter
的序列化逻辑迁移到更现代、更安全的替代方案(如System.Text.Json
或Protobuf)。这可能需要投入时间和资源,但从安全性和维护性角度来看,这是值得的。
记住,安全是一个持续的过程,而不是一劳永逸的解决方案。对于像
BinaryFormatter这样已知存在高风险的工具,除非万不得已,否则应坚决避免使用。










