注意事項
BinaryFormatter サポートは推奨されません。 これは、新しいタイプ セーフな API にすぐに移行できないレガシ アプリケーションの一時的な移行ブリッジとしてのみ使用します。 このアプローチでは、重大なセキュリティ リスクが伴います。
この記事では、.NET 10 で Windows フォーム クリップボード操作の制限付き BinaryFormatter サポートを構成する方法について説明します。 セキュリティの脆弱性により、 BinaryFormatter は .NET 9 のランタイムから削除されましたが、移行に時間が必要なレガシ アプリケーションの明示的な構成によって制限された機能を復元します。
新しいタイプ セーフ API への完全な移行ガイダンスについては、 .NET 10 での Windows フォーム クリップボードと DataObject の変更に関する説明を参照してください。
Important
このコンテンツは、特に指定がない限り、最新の .NET にのみ適用され、.NET Framework には適用されません 。
[前提条件]
続行する前に、次の概念を確認してください。
- 現在、アプリケーションがクリップボード操作で
BinaryFormatterを使用する方法。 -
BinaryFormatterの削除につながったセキュリティの脆弱性。 - 新しいタイプセーフクリップボード API への移行タイムライン。
詳細については、次の記事を参照してください。
セキュリティの警告とリスク
BinaryFormatter は本質的に安全でなくなり、次の理由で非推奨になります。
- 任意のコード実行の脆弱性: 攻撃者は逆シリアル化中に悪意のあるコードを実行し、アプリケーションをリモート攻撃にさらす可能性があります。
- サービス拒否攻撃: 悪意のあるクリップボード データは、過剰なメモリや CPU リソースを消費し、クラッシュや不安定を引き起こす可能性があります。
- 情報漏えいのリスク: 攻撃者はメモリから機密データを抽出する可能性があります。
- セキュリティ境界なし: この形式は基本的に安全ではなく、構成設定ではセキュリティで保護できません。
このサポートは、新しいタイプ セーフ API を使用するようにアプリケーションを更新するときに、一時的なブリッジとしてのみ有効にします。
互換性パッケージをインストールする
サポートされていない BinaryFormatter 互換性パッケージをプロジェクトに追加します。 このパッケージは、 BinaryFormatter 操作に必要なランタイム サポートを提供します。
<ItemGroup>
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0*-*"/>
</ItemGroup>
注
このパッケージは、サポートされていないと非推奨としてマークされています。 移行中の一時的な互換性のためにのみ使用します。
プロジェクトで安全でないシリアル化を有効にする
EnableUnsafeBinaryFormatterSerialization プロパティをプロジェクト ファイルにtrueするように設定します。 このプロパティは、 BinaryFormatter の使用を許可するようにコンパイラに指示します。
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>
この設定がない場合、アプリケーションでは、 BinaryFormatter API を使用しようとしたときにコンパイル エラーが生成されます。
Windows フォーム ランタイム スイッチを構成する
アプリケーションの runtimeconfig.json ファイルを作成または更新して、Windows フォーム固有のクリップボードの切り替えを有効にします。 この構成により、必要に応じてクリップボード操作を BinaryFormatter にフォールバックできます。
{
"runtimeOptions": {
"configProperties": {
"Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization": true
}
}
}
Important
この特定のランタイム スイッチがないと、一般的なシリアル化のサポートが有効になっている場合でも、クリップボード操作は BinaryFormatter にフォールバックしません。 このスイッチは、Windows フォームと WPF クリップボードの機能に特に必要です。
セキュリティに重点を置いた型リゾルバーを実装する
BinaryFormatterが有効になっている場合でも、型リゾルバーを実装して、逆シリアル化を明示的に承認された型に制限します。 型リゾルバーは、悪意のあるペイロード攻撃に対する唯一の防御を提供します。
セキュリティで保護された型リゾルバーを作成する
// Create a security-focused type resolver
private static Type SecureTypeResolver(TypeName typeName)
{
// Explicit allow-list of permitted types—add only what you need
var allowedTypes = new Dictionary<string, Type>
{
["MyApp.Person"] = typeof(Person),
["MyApp.AppSettings"] = typeof(AppSettings),
["System.String"] = typeof(string),
["System.Int32"] = typeof(int),
// Add only the specific types your application requires
};
// Only allow explicitly listed types - exact string match required
if (allowedTypes.TryGetValue(typeName.FullName, out Type allowedType))
{
return allowedType;
}
// Reject any type not in the allow-list with clear error message
throw new InvalidOperationException(
$"Type '{typeName.FullName}' is not permitted for clipboard deserialization");
}
' Create a security-focused type resolver
Private Shared Function SecureTypeResolver(typeName As TypeName) As Type
' Explicit allow-list of permitted types—add only what you need
Dim allowedTypes As New Dictionary(Of String, Type) From {
{"MyApp.Person", GetType(Person)},
{"MyApp.AppSettings", GetType(AppSettings)},
{"System.String", GetType(String)},
{"System.Int32", GetType(Integer)}
} ' Add only the specific types your application requires
' Only allow explicitly listed types - exact string match required
Dim allowedType As Type = Nothing
If allowedTypes.TryGetValue(typeName.FullName, allowedType) Then
Return allowedType
End If
' Reject any type not in the allow-list with clear error message
Throw New InvalidOperationException(
$"Type '{typeName.FullName}' is not permitted for clipboard deserialization")
End Function
クリップボード操作で型リゾルバーを使用する
// Use the resolver with clipboard operations
private static Type SecureTypeResolver(TypeName typeName)
{
// Implementation from SecureTypeResolver example
// ... (allow-list implementation here)
throw new InvalidOperationException($"Type '{typeName.FullName}' is not permitted");
}
public static void UseSecureTypeResolver()
{
// Retrieve legacy data using the secure type resolver
if (Clipboard.TryGetData("LegacyData", SecureTypeResolver, out MyCustomType data))
{
ProcessLegacyData(data);
}
else
{
Console.WriteLine("No compatible data found on clipboard");
}
}
' Use the resolver with clipboard operations
Private Shared Function SecureTypeResolver(typeName As TypeName) As Type
' Implementation from SecureTypeResolver example
' ... (allow-list implementation here)
Throw New InvalidOperationException($"Type '{typeName.FullName}' is not permitted")
End Function
Public Shared Sub UseSecureTypeResolver()
' Retrieve legacy data using the secure type resolver
Dim data As MyCustomType = Nothing
If Clipboard.TryGetData("LegacyData", AddressOf SecureTypeResolver, data) Then
ProcessLegacyData(data)
Else
Console.WriteLine("No compatible data found on clipboard")
End If
End Sub
型リゾルバーのセキュリティ ガイドライン
型リゾルバーを実装する場合は、次の重要なセキュリティ ガイドラインに従ってください。
明示的な許可リストを使用する
- 既定では拒否: 明示的にリストされている型のみを許可します。
- ワイルドカードなし: パターン マッチングまたは名前空間ベースのアクセス許可を避けます。
- 完全一致: 型名に完全一致する文字列が必要です。
すべての入力を検証する
- 型名の検証: 型名が想定される形式と一致していることを確認します。
- アセンブリの制限: 既知の信頼できるアセンブリに型を制限します。
- バージョン チェック: バージョン固有の型の制限を検討してください。
不明な型を安全に処理する
- 例外をスローする: 常に許可されていない型に対してスローします。
- ログの試行: 未承認のアクセス試行をログに記録することを検討してください。
- エラー メッセージをクリアする: デバッグの特定の拒否理由を指定します。
定期的なメンテナンス
- 監査を定期的に行う: 許可される型リストを確認して更新します。
- 使用されていない型を削除する: 不要になった型のアクセス許可を削除します。
- ドキュメントの決定: 各種類が許可される理由の明確なドキュメントを保持します。
構成をテストする
BinaryFormatterサポートを構成した後、アプリケーションをテストして正しく動作することを確認します。
- クリップボード操作を確認する: カスタム型を使用してデータの格納と取得の両方をテストします。
- テスト型リゾルバー: 承認されていない型が正しく拒否されていることを確認します。
- セキュリティの監視: 予期しない型解決の試みを監視します。
- パフォーマンス テスト: 型リゾルバーがパフォーマンスに大きな影響を与えないことを確認します。
public static void TestBinaryFormatterConfiguration()
{
// Test data to verify configuration
var testPerson = new Person { Name = "Test User", Age = 30 };
try
{
// Test storing data (this should work with proper configuration)
Clipboard.SetData("TestPerson", testPerson);
Console.WriteLine("Successfully stored test data on clipboard");
// Test retrieving with type resolver
if (Clipboard.TryGetData("TestPerson", SecureTypeResolver, out Person retrievedPerson))
{
Console.WriteLine($"Successfully retrieved: {retrievedPerson.Name}, Age: {retrievedPerson.Age}");
}
else
{
Console.WriteLine("Failed to retrieve test data");
}
// Test that unauthorized types are rejected
try
{
Clipboard.TryGetData("TestPerson", UnauthorizedTypeResolver, out Person _);
Console.WriteLine("ERROR: Unauthorized type was not rejected!");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"SUCCESS: Unauthorized type properly rejected - {ex.Message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Configuration test failed: {ex.Message}");
}
}
private static Type SecureTypeResolver(TypeName typeName)
{
var allowedTypes = new Dictionary<string, Type>
{
["ClipboardExamples.Person"] = typeof(Person),
};
if (allowedTypes.TryGetValue(typeName.FullName, out Type allowedType))
{
return allowedType;
}
throw new InvalidOperationException($"Type '{typeName.FullName}' is not permitted");
}
private static Type UnauthorizedTypeResolver(TypeName typeName)
{
// Intentionally restrictive resolver to test rejection
throw new InvalidOperationException($"No types are permitted by this test resolver");
}
Public Shared Sub TestBinaryFormatterConfiguration()
' Test data to verify configuration
Dim testPerson As New Person With {.Name = "Test User", .Age = 30}
Try
' Test storing data (this should work with proper configuration)
Clipboard.SetData("TestPerson", testPerson)
Console.WriteLine("Successfully stored test data on clipboard")
' Test retrieving with type resolver
Dim retrievedPerson As Person = Nothing
If Clipboard.TryGetData("TestPerson", AddressOf SecureTypeResolver, retrievedPerson) Then
Console.WriteLine($"Successfully retrieved: {retrievedPerson.Name}, Age: {retrievedPerson.Age}")
Else
Console.WriteLine("Failed to retrieve test data")
End If
' Test that unauthorized types are rejected
Try
Dim testResult As Person = Nothing
Clipboard.TryGetData("TestPerson", AddressOf UnauthorizedTypeResolver, testResult)
Console.WriteLine("ERROR: Unauthorized type was not rejected!")
Catch ex As InvalidOperationException
Console.WriteLine($"SUCCESS: Unauthorized type properly rejected - {ex.Message}")
End Try
Catch ex As Exception
Console.WriteLine($"Configuration test failed: {ex.Message}")
End Try
End Sub
Private Shared Function SecureTypeResolver(typeName As TypeName) As Type
Dim allowedTypes As New Dictionary(Of String, Type) From {
{"ClipboardExamples.Person", GetType(Person)}
}
Dim allowedType As Type = Nothing
If allowedTypes.TryGetValue(typeName.FullName, allowedType) Then
Return allowedType
End If
Throw New InvalidOperationException($"Type '{typeName.FullName}' is not permitted")
End Function
Private Shared Function UnauthorizedTypeResolver(typeName As TypeName) As Type
' Intentionally restrictive resolver to test rejection
Throw New InvalidOperationException($"No types are permitted by this test resolver")
End Function
移行戦略を計画する
BinaryFormatterサポートでは一時的な互換性が提供されますが、新しいタイプ セーフな API に移行するための移行計画を作成します。
- 使用状況を特定する: カスタム型を使用して、すべてのクリップボード操作をカタログ化します。
- 移行に優先順位を付ける: 最初に最もセキュリティに依存する操作に重点を置く。
- 増分更新: リスクを軽減するために、一度に 1 つの操作を移行します。
- 十分にテストする: 新しい実装で同等の機能が提供されていることを確認します。
- BinaryFormatter の削除: 移行が完了したら、サポートを無効にします。
リソースをクリーンアップする
新しいタイプ セーフなクリップボード API に移行したら、 BinaryFormatter 構成を削除してセキュリティを強化します。
-
System.Runtime.Serialization.Formattersパッケージ参照を削除します。 - プロジェクト ファイルから
EnableUnsafeBinaryFormatterSerializationプロパティを削除します。 -
runtimeconfig.jsonからWindows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization設定を削除します。 - 不要になった型リゾルバーの実装を削除します。
関連コンテンツ
.NET Desktop feedback