Udostępnij przez


Włącz obsługę schowka BinaryFormatter (niezalecane)

Ostrzeżenie

BinaryFormatter wsparcie nie jest zalecane. Używaj go tylko jako tymczasowego pomostu migracyjnego dla aplikacji starszego typu, które nie mogą natychmiast migrować do nowych typów interfejsów API bezpiecznych. Takie podejście niesie ze sobą znaczące zagrożenia bezpieczeństwa.

W tym artykule pokazano, jak skonfigurować obsługę operacji schowka Windows Forms w ograniczonym zakresie w .NET 10. Mimo że BinaryFormatter zostało usunięte ze środowiska uruchomieniowego platformy .NET 9 z powodu luk w zabezpieczeniach, przywróć ograniczoną funkcjonalność poprzez jawną konfigurację dla starszych aplikacji, które potrzebują czasu na migrację.

Aby uzyskać pełne wskazówki dotyczące migracji do nowych interfejsów API bezpiecznych typów, zobacz Schowek formularzy systemu Windows i zmiany obiektu DataObject na platformie .NET 10.

Ważne

Ta zawartość dotyczy tylko nowoczesnej platformy .NET, a nie .NET Framework, chyba że określono inaczej.

Wymagania wstępne

Przed kontynuowaniem zapoznaj się z następującymi pojęciami:

  • Sposób, w jaki aplikacja obecnie używa BinaryFormatter w operacjach schowka.
  • Luki w zabezpieczeniach, które doprowadziły do usunięcia elementu BinaryFormatter.
  • Oś czasu migracji do nowych interfejsów API schowka bezpiecznego typu.

Aby uzyskać więcej informacji, zobacz następujące artykuły:

Ostrzeżenia i zagrożenia dotyczące zabezpieczeń

BinaryFormatter jest z natury niezabezpieczony i przestarzały z następujących powodów:

  • Luki w zabezpieczeniach dotyczące dowolnego wykonywania kodu: osoby atakujące mogą wykonywać złośliwy kod podczas deserializacji, ujawniając aplikację na ataki zdalne.
  • Ataki typu "odmowa usługi": złośliwe dane schowka mogą zużywać nadmiarną ilość pamięci lub zasobów CPU, powodując awarie lub niestabilność.
  • Ryzyko ujawnienia informacji: osoby atakujące mogą wyodrębniać poufne dane z pamięci.
  • Brak granic zabezpieczeń: format jest zasadniczo niebezpieczny, a ustawienia konfiguracji nie mogą go zabezpieczyć.

Włącz tę obsługę tylko jako most tymczasowy podczas aktualizowania aplikacji, aby korzystać z nowych bezpiecznych typów interfejsów API.

Instalowanie pakietu zgodności

Dodaj niewspierany pakiet zgodności BinaryFormatter do swojego projektu. Ten pakiet zapewnia wymaganą obsługę środowiska uruchomieniowego dla BinaryFormatter operacji:

<ItemGroup>
  <PackageReference Include="System.Runtime.Serialization.Formatters" Version="10.0.0*-*"/>
</ItemGroup>

Uwaga / Notatka

Ten pakiet jest oznaczony jako nieobsługiwany i przestarzały. Używaj go tylko w celu zapewnienia zgodności tymczasowej podczas migracji.

Włączanie niebezpiecznej serializacji w projekcie

Ustaw właściwość EnableUnsafeBinaryFormatterSerialization na true w pliku twojego projektu. Ta właściwość nakazuje kompilatorowi zezwolenie na BinaryFormatter użycie:

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>

Bez tego ustawienia aplikacja generuje błędy kompilacji podczas próby użycia BinaryFormatter interfejsów API.

Skonfiguruj przełącznik środowiska uruchomieniowego formularzy systemu Windows

Utwórz lub zaktualizuj plik aplikacji runtimeconfig.json , aby włączyć przełącznik schowka specyficzny dla formularzy systemu Windows. Ta konfiguracja umożliwia wykonywanie operacji schowka w razie potrzeby, przełączając się na BinaryFormatter.

{
  "runtimeOptions": {
    "configProperties": {
      "Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization": true
    }
  }
}

Ważne

Bez tego konkretnego przełącznika środowiska uruchomieniowego operacje schowka nie wrócą do BinaryFormatter, nawet jeśli włączono obsługę serializacji ogólnej. Przełącznik ten jest niezbędny konkretnie dla funkcjonalności schowka w Windows Forms i WPF.

Implementowanie rozpoznawania typów skoncentrowanych na zabezpieczeniach

Nawet przy włączonym BinaryFormatter zaimplementuj metody rozpoznawania typów, aby ograniczyć deserializację do jawnie zatwierdzonych typów. Programy rozpoznawania typów zapewniają jedyną ochronę przed złośliwymi atakami ładunku.

Utwórz bezpieczny rozwiązywacz typów

// 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

Użyj narzędzia rozpoznawania typów z operacjami schowka

// 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

Wytyczne dotyczące zabezpieczeń dla rozpoznawania typów

Postępuj zgodnie z tymi podstawowymi wytycznymi dotyczącymi zabezpieczeń podczas implementowania rozpoznawania typów:

Używanie jawnych list dozwolonych

  • Odrzuć domyślnie: Zezwalaj tylko na jawne typy.
  • Nie używaj symboli wieloznacznych: unikaj dopasowywania wzorców lub stosowania uprawnień opartych na przestrzeni nazw.
  • Dokładne dopasowanie: Wymagaj dokładnego dopasowania ciągów znaków dla nazw typów.

Weryfikowanie wszystkich danych wejściowych

  • Sprawdzanie poprawności nazwy typu: upewnij się, że nazwy typów są zgodne z oczekiwanymi formatami.
  • Ograniczenia zestawów: ogranicz typy znanych, zaufanych zestawów.
  • Sprawdzanie wersji: rozważ ograniczenia typu specyficzne dla wersji.

Bezpieczna obsługa nieznanych typów

  • Zgłaszanie wyjątków: zawsze zgłaszaj nieautoryzowane typy.
  • Próby rejestrowania: rozważ rejestrowanie nieautoryzowanych prób dostępu.
  • Wyczyść komunikaty o błędach: podaj konkretne przyczyny odrzucenia debugowania.

Regularna konserwacja

  • Regularnie przeprowadzaj inspekcję: Przejrzyj i zaktualizuj listę dozwolonych typów.
  • Usuń nieużywane typy: eliminowanie uprawnień dla typów, które nie są już potrzebne.
  • Decyzje dotyczące dokumentów: zachowaj wyraźną dokumentację, dlaczego każdy typ jest dozwolony.

Testowanie konfiguracji

Po skonfigurowaniu obsługi przetestuj aplikację BinaryFormatter, aby upewnić się, że działa prawidłowo.

  1. Sprawdź operacje schowka: przetestuj zarówno przechowywanie, jak i pobieranie danych przy użyciu typów niestandardowych.
  2. Program rozpoznawania typów testów: upewnij się, że nieautoryzowane typy są prawidłowo odrzucane.
  3. Monitorowanie zabezpieczeń: obserwuj wszelkie nieoczekiwane próby rozwiązania typu.
  4. Testowanie wydajności: Upewnij się, że program rozpoznawania typów nie ma znaczącego wpływu na wydajność.
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

Planowanie strategii migracji

Chociaż obsługa BinaryFormatter zapewnia tymczasową zgodność, opracuj plan migracji dla przejścia do nowych interfejsów API z bezpiecznymi typami.

  1. Identyfikowanie użycia: Kataloguj wszystkie operacje schowka przy użyciu typów niestandardowych.
  2. Priorytet migracji: najpierw skoncentruj się na najbardziej wrażliwych na zabezpieczeniach operacjach.
  3. Aktualizuj przyrostowo: przeprowadź migrację jednej operacji naraz, aby zmniejszyć ryzyko.
  4. Dokładnie przetestuj: Upewnij się, że nowe implementacje zapewniają równoważne funkcje.
  5. Usuń binaryFormatter: Wyłącz obsługę po zakończeniu migracji.

Uprzątnij zasoby

Po przeprowadzeniu migracji do nowych interfejsów API schowka bezpiecznego typu usuń konfigurację BinaryFormatter w celu zwiększenia bezpieczeństwa:

  1. Usuń odwołanie do System.Runtime.Serialization.Formatters pakietu.
  2. EnableUnsafeBinaryFormatterSerialization Usuń właściwość z pliku projektu.
  3. Usuń ustawienie Windows.ClipboardDragDrop.EnableUnsafeBinaryFormatterSerialization z runtimeconfig.json.
  4. Usuń implementacje rozpoznawania typów, które nie są już potrzebne.