BinaryFormatter (非推奨)

シリアライゼーションC#.NETバイナリ形式非推奨セキュリティリスク

ライブラリ

BinaryFormatter (非推奨)

概要

BinaryFormatterは、.NET Frameworkで長年使用されてきたバイナリシリアライゼーションライブラリです。しかし、深刻なセキュリティ脆弱性が発見されたため、.NET 5以降では非推奨となり、.NET 9では完全に削除されました。リモートコード実行の脆弱性により、信頼できないデータの逆シリアル化が攻撃の入口となる可能性があります。既存のアプリケーションは、System.Text.Json、MessagePack、protobuf-netなどの安全な代替手段への移行が必須です。

詳細

BinaryFormatterは、.NETオブジェクトをバイナリ形式でシリアライズする最も古いメカニズムの一つでした。型情報を含む完全なオブジェクトグラフのシリアライゼーションが可能で、循環参照や複雑な継承階層もサポートしていました。しかし、その柔軟性が仇となり、悪意のあるペイロードの実行を可能にする脆弱性(CVE-2021-42321、CVE-2019-1306など)が次々と発見されました。Microsoftは段階的な廃止計画を実施し、現在は完全に使用を禁止しています。

主な特徴(過去の機能)

  • 完全なオブジェクトグラフ: 循環参照を含む複雑な構造の保存
  • 型情報の保持: アセンブリ情報を含む完全な型復元
  • バージョン管理: ISerializableインターフェースによるカスタマイズ
  • ストリーム対応: FileStream、MemoryStreamとの統合
  • RemotingやWCF統合: 分散アプリケーションでの使用
  • 深いクローニング: オブジェクトの完全コピー作成

メリット・デメリット

メリット(歴史的な観点)

  • .NET Frameworkとの深い統合
  • 複雑なオブジェクトグラフの完全なサポート
  • 最小限のコードで使用可能
  • 循環参照の自動処理
  • カスタムシリアライゼーションの柔軟性
  • レガシーシステムとの互換性

デメリット(重要な警告)

  • 深刻なセキュリティ脆弱性(リモートコード実行)
  • .NET 5以降で非推奨、.NET 9で削除
  • 信頼できないデータの処理が危険
  • クロスプラットフォーム非対応
  • バイナリ形式のため可読性なし
  • デバッグとトラブルシューティングが困難

参考ページ

書き方の例

警告:以下のコードは学習目的のみ

// 警告:BinaryFormatterは使用しないでください!
// 以下は移行前のレガシーコードの例です

#pragma warning disable SYSLIB0011 // 型またはメンバーが旧型式です

using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
public class LegacyData
{
    public string Name { get; set; }
    public int Value { get; set; }
}

// 旧式のシリアライゼーション(使用禁止)
var formatter = new BinaryFormatter();
using (var stream = new MemoryStream())
{
    var data = new LegacyData { Name = "Test", Value = 42 };
    formatter.Serialize(stream, data); // セキュリティリスク!
}

System.Text.Jsonへの移行例

using System.Text.Json;

public class ModernData
{
    public string Name { get; set; }
    public int Value { get; set; }
}

// 安全なJSON シリアライゼーション
var data = new ModernData { Name = "Test", Value = 42 };

// シリアライズ
string jsonString = JsonSerializer.Serialize(data);
await File.WriteAllTextAsync("data.json", jsonString);

// デシリアライズ
string readJson = await File.ReadAllTextAsync("data.json");
var restoredData = JsonSerializer.Deserialize<ModernData>(readJson);

// オプション付き
var options = new JsonSerializerOptions
{
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
string prettyJson = JsonSerializer.Serialize(data, options);

MessagePackへの移行例

using MessagePack;

[MessagePackObject]
public class MessagePackData
{
    [Key(0)]
    public string Name { get; set; }
    
    [Key(1)]
    public int Value { get; set; }
}

// MessagePackシリアライゼーション
var data = new MessagePackData { Name = "Test", Value = 42 };

// バイナリシリアライズ
byte[] bytes = MessagePackSerializer.Serialize(data);
await File.WriteAllBytesAsync("data.msgpack", bytes);

// デシリアライズ
byte[] readBytes = await File.ReadAllBytesAsync("data.msgpack");
var restoredData = MessagePackSerializer.Deserialize<MessagePackData>(readBytes);

protobuf-netへの移行例

using ProtoBuf;

[ProtoContract]
public class ProtoData
{
    [ProtoMember(1)]
    public string Name { get; set; }
    
    [ProtoMember(2)]
    public int Value { get; set; }
}

// Protocol Buffersシリアライゼーション
var data = new ProtoData { Name = "Test", Value = 42 };

// ファイルへシリアライズ
using (var file = File.Create("data.proto"))
{
    Serializer.Serialize(file, data);
}

// デシリアライズ
using (var file = File.OpenRead("data.proto"))
{
    var restoredData = Serializer.Deserialize<ProtoData>(file);
}

カスタムシリアライゼーションの移行

// 旧: ISerializable実装(BinaryFormatter用)
[Serializable]
public class OldCustomData : ISerializable
{
    // 非推奨の実装...
}

// 新: System.Text.Jsonカスタムコンバーター
public class CustomDataConverter : JsonConverter<CustomData>
{
    public override CustomData Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        // カスタム読み取りロジック
        var jsonObject = JsonSerializer.Deserialize<JsonElement>(ref reader);
        return new CustomData
        {
            // プロパティマッピング
        };
    }

    public override void Write(
        Utf8JsonWriter writer, 
        CustomData value, 
        JsonSerializerOptions options)
    {
        // カスタム書き込みロジック
        writer.WriteStartObject();
        // プロパティ書き込み
        writer.WriteEndObject();
    }
}

安全性チェックリスト

// BinaryFormatter使用箇所の検出と置換

// 1. プロジェクト全体でBinaryFormatterの使用を検索
// Search: "BinaryFormatter" または "ISerializable"

// 2. 警告を有効化(.csprojファイル)
// <PropertyGroup>
//   <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
// </PropertyGroup>

// 3. 代替ライブラリの選択基準
public static class SerializerSelection
{
    public static ISerializer SelectSerializer(RequirementsContext context)
    {
        if (context.NeedsHumanReadable)
            return new SystemTextJsonSerializer();
        
        if (context.NeedsHighPerformance)
            return new MessagePackSerializer();
        
        if (context.NeedsSchemaEvolution)
            return new ProtobufNetSerializer();
        
        if (context.NeedsCompression)
            return new MemoryPackSerializer();
        
        // デフォルト(安全で汎用的)
        return new SystemTextJsonSerializer();
    }
}