C#的BinaryFormatter如何序列化对象?

小老鼠
发布: 2025-08-05 09:27:02
原创
601人浏览过

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

C#的BinaryFormatter如何序列化对象?

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
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的固有风险,它们只是在特定限制下提供一些缓解。最根本的原则是:绝不反序列化来自不可信源的数据!

  1. 限制反序列化的类型(

    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);
    登录后复制

    这个方法可以有效阻止恶意类型被实例化,但它要求你对所有可能被序列化的类型有清晰的白名单。

  2. 数据完整性校验:在反序列化之前,对数据流进行完整性校验,例如通过计算哈希值并与已知安全哈希值进行比对。如果哈希值不匹配,则拒绝反序列化。这可以防止数据在传输或存储过程中被篡改,但无法阻止攻击者构造新的恶意负载。

  3. 最小权限原则:运行序列化/反序列化代码的进程,应以尽可能低的权限运行,限制其对文件系统、网络或其他资源的访问能力。即使发生RCE,也能限制其造成的损害。

  4. 隔离处理:如果可能,将

    BinaryFormatter
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的反序列化操作放在一个独立的、沙箱化的进程中。即使该进程被攻破,也难以影响到主应用程序或其他关键系统。

  5. 尽快迁移:以上所有措施都是权宜之计。长远来看,最根本的解决方案是逐步将所有使用

    BinaryFormatter
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的序列化逻辑迁移到更现代、更安全的替代方案(如
    System.Text.Json
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    或Protobuf)。这可能需要投入时间和资源,但从安全性和维护性角度来看,这是值得的。

记住,安全是一个持续的过程,而不是一劳永逸的解决方案。对于像

BinaryFormatter
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
这样已知存在高风险的工具,除非万不得已,否则应坚决避免使用。

以上就是C#的BinaryFormatter如何序列化对象?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号