CA2312 : Vérifiez que NetDataContractSerializer.Binder est défini avant la désérialisation

Propriété Value
Identificateur de la règle CA2312
Titre Vérifiez que NetDataContractSerializer.Binder est défini avant la désérialisation
Catégorie Sécurité
Le correctif est cassant ou non cassant Sans rupture
Activé par défaut dans .NET 8 Non

Cause

Une méthode de désérialisation System.Runtime.Serialization.NetDataContractSerializer a été appelée ou référencée, et la propriété Binder peut être Null.

Cette règle est similaire à CA2311, mais l’analyse ne peut pas déterminer si Binder est à coup sûr Null.

Par défaut, cette règle analyse l’intégralité du codebase, mais elle est configurable.

Avertissement

La restriction des types avec un serializationBinder ne peut pas prévenir toutes les attaques. Pour plus d’informations, consultez le Guide de sécurité de BinaryFormatter.

Description de la règle

Les désérialiseurs non sécurisés sont vulnérables lors de la désérialisation de données non approuvées. Un attaquant peut modifier les données sérialisées pour y inclure des types inattendus afin d’injecter des objets avec des effets secondaires malveillants. Une attaque contre un désérialiseur non sécurisé peut, par exemple, exécuter des commandes sur le système d’exploitation sous-jacent, communiquer sur le réseau ou supprimer des fichiers.

Cette règle recherche des références ou des appels de méthode de désérialisation System.Runtime.Serialization.NetDataContractSerializer lorsque Binder peut avoir la valeur Null. Si vous souhaitez interdire toute désérialisation avec NetDataContractSerializer quelle que soit la propriété Binder, désactivez cette règle et CA2311, puis activez la règle CA2310.

NetDataContractSerializer n’est pas sécurisé et ne peut pas être sécurisé. Pour plus d’informations, consultez le Guide de sécurité de BinaryFormatter.

Comment corriger les violations

  • Utilisez plutôt un sérialiseur sécurisé, et n’autorisez pas un attaquant à spécifier un type arbitraire à désérialiser. Pour plus d’informations, consultez les alternatives préférées.
  • Rendez les données sérialisées inviolables. Après la sérialisation, signez par chiffrement les données sérialisées. Avant la désérialisation, validez la signature de chiffrement. Protégez la clé de chiffrement contre la divulgation, et concevez un mécanisme de permutation des clés.
  • Cette option rend le code vulnérable aux attaques par déni de service et à d’éventuelles attaques d’exécution de code à distance à l’avenir. Pour plus d’informations, consultez le Guide de sécurité de BinaryFormatter. Restreignez les types désérialisés. Implémentez un System.Runtime.Serialization.SerializationBinder personnalisé. Avant la désérialisation, définissez la propriété Binder sur une instance de votre SerializationBinder personnalisé dans tous les chemins de code. Dans la méthode BindToType substituée, si le type est inattendu, levez une exception pour arrêter la désérialisation.

Quand supprimer les avertissements

NetDataContractSerializer n’est pas sécurisé et ne peut pas être sécurisé.

Configurer le code à analyser

Utilisez l’option suivante pour configurer les parties de votre codebase sur lesquelles exécuter cette règle.

Vous pouvez configurer ces options pour cette règle uniquement, pour toutes les règles auxquelles elles s’appliquent ou pour toutes les règles de cette catégorie (Sécurité) auxquelles elles s’appliquent. Pour plus d’informations, consultez Options de configuration des règles de qualité du code.

Exclure des symboles spécifiques

Vous pouvez exclure de l’analyse des symboles spécifiques, comme des types et des méthodes. Par exemple, pour spécifier que la règle ne doit pas s’exécuter sur du code dans des types nommés MyType, ajoutez la paire clé-valeur suivante à un fichier .editorconfig dans votre projet :

dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType

Formats de nom de symbole autorisés dans la valeur d’option (séparés par |) :

  • Nom du symbole uniquement (inclut tous les symboles avec le nom, quel que soit le type ou l’espace de noms qui les contient).
  • Noms qualifiés complets au format d’ID de documentation du symbole. Chaque nom de symbole nécessite un préfixe de type symbole, comme M: pour les méthodes, T: pour les types et N: pour les espaces de noms.
  • .ctor pour les constructeurs et .cctor pour les constructeurs statiques.

Exemples :

Valeur d’option Récapitulatif
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType Correspond à tous les symboles nommés MyType.
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType1|MyType2 Correspond à tous les symboles nommés MyType1 ou MyType2.
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) Correspond à une méthode MyMethod spécifique avec la signature complète spécifiée.
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) Correspond à des méthodes MyMethod1 et MyMethod2 spécifiques avec la signature complète spécifiée.

Exclure des types spécifiques et leurs types dérivés

Vous pouvez exclure de l’analyse des types spécifiques et leurs types dérivés. Par exemple, pour spécifier que la règle ne doit s’exécuter sur aucune méthode dans des types nommés MyType et leurs types dérivés, ajoutez la paire clé-valeur suivante à un fichier .editorconfig dans votre projet :

dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType

Formats de nom de symbole autorisés dans la valeur d’option (séparés par |) :

  • Nom du type uniquement (inclut tous les types avec le nom, quel que soit le type ou l’espace de noms qui les contient).
  • Noms qualifiés complets au format d’ID de documentation du symbole, avec un préfixe T: facultatif.

Exemples :

Valeur d’option Récapitulatif
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType Correspond à tous les types nommés MyType et à tous leurs types dérivés.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType1|MyType2 Correspond à tous les types nommés MyType1 ou MyType2, et à tous leurs types dérivés.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS.MyType Correspond à un type MyType spécifique avec un nom complet donné et tous ses types dérivés.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS1.MyType1|M:NS2.MyType2 Correspond à des types MyType1 ou MyType2 spécifiques avec leur nom complet respectif et tous leurs types dérivés.

Exemples de pseudo-code

Violation 1

using System;
using System.IO;
using System.Runtime.Serialization;

public class BookRecordSerializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        // One way to discover expected types is through testing deserialization
        // of **valid** data and logging the types used.

        ////Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')");

        if (typeName == "BookRecord")
        {
            return typeof(BookRecord);
        }
        else if (typeName == "AisleLocation")
        {
            return typeof(AisleLocation);
        }
        else
        {
            throw new ArgumentException("Unexpected type", nameof(typeName));
        }
    }
}

[DataContract]
public class BookRecord
{
    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public AisleLocation Location { get; set; }
}

[DataContract]
public class AisleLocation
{
    [DataMember]
    public char Aisle { get; set; }

    [DataMember]
    public byte Shelf { get; set; }
}

public class Binders
{
    public static SerializationBinder BookRecord = new BookRecordSerializationBinder();
}

public class ExampleClass
{
    public BookRecord DeserializeBookRecord(byte[] bytes)
    {
        NetDataContractSerializer serializer = new NetDataContractSerializer();
        serializer.Binder = Binders.BookRecord;
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            return (BookRecord) serializer.Deserialize(ms);
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization

Public Class BookRecordSerializationBinder
    Inherits SerializationBinder

    Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
        ' One way to discover expected types is through testing deserialization
        ' of **valid** data and logging the types used.

        'Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')")

        If typeName = "BinaryFormatterVB.BookRecord" Then
            Return GetType(BookRecord)
        Else If typeName = "BinaryFormatterVB.AisleLocation" Then
            Return GetType(AisleLocation)
        Else
            Throw New ArgumentException("Unexpected type", NameOf(typeName))
        End If
    End Function
End Class

<DataContract()>
Public Class BookRecord
    <DataMember()>
    Public Property Title As String

    <DataMember()>
    Public Property Location As AisleLocation
End Class

<DataContract()>
Public Class AisleLocation
    <DataMember()>
    Public Property Aisle As Char

    <DataMember()>
    Public Property Shelf As Byte
End Class

Public Class Binders
    Public Shared Property BookRecord As SerializationBinder = New BookRecordSerializationBinder()
End Class


Public Class ExampleClass
    Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
        Dim serializer As NetDataContractSerializer = New NetDataContractSerializer()
        serializer.Binder = Binders.BookRecord
        Using ms As MemoryStream = New MemoryStream(bytes)
            Return CType(serializer.Deserialize(ms), BookRecord)   ' CA2312 violation
        End Using
    End Function
End Class

Solution 1

using System;
using System.IO;
using System.Runtime.Serialization;

public class BookRecordSerializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        // One way to discover expected types is through testing deserialization
        // of **valid** data and logging the types used.

        ////Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')");

        if (typeName == "BookRecord")
        {
            return typeof(BookRecord);
        }
        else if (typeName == "AisleLocation")
        {
            return typeof(AisleLocation);
        }
        else
        {
            throw new ArgumentException("Unexpected type", nameof(typeName));
        }
    }
}

[DataContract]
public class BookRecord
{
    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public AisleLocation Location { get; set; }
}

[DataContract]
public class AisleLocation
{
    [DataMember]
    public char Aisle { get; set; }

    [DataMember]
    public byte Shelf { get; set; }
}

public class Binders
{
    public static SerializationBinder BookRecord = new BookRecordSerializationBinder();
}

public class ExampleClass
{
    public BookRecord DeserializeBookRecord(byte[] bytes)
    {
        NetDataContractSerializer serializer = new NetDataContractSerializer();

        // Ensure that Binder is always non-null before deserializing
        serializer.Binder = Binders.BookRecord ?? throw new Exception("Expected non-null");

        using (MemoryStream ms = new MemoryStream(bytes))
        {
            return (BookRecord) serializer.Deserialize(ms);
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization

Public Class BookRecordSerializationBinder
    Inherits SerializationBinder

    Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
        ' One way to discover expected types is through testing deserialization
        ' of **valid** data and logging the types used.

        'Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')")

        If typeName = "BinaryFormatterVB.BookRecord" Then
            Return GetType(BookRecord)
        Else If typeName = "BinaryFormatterVB.AisleLocation" Then
            Return GetType(AisleLocation)
        Else
            Throw New ArgumentException("Unexpected type", NameOf(typeName))
        End If
    End Function
End Class

<DataContract()>
Public Class BookRecord
    <DataMember()>
    Public Property Title As String

    <DataMember()>
    Public Property Location As AisleLocation
End Class

<DataContract()>
Public Class AisleLocation
    <DataMember()>
    Public Property Aisle As Char

    <DataMember()>
    Public Property Shelf As Byte
End Class

Public Class Binders
    Public Shared Property BookRecord As SerializationBinder = New BookRecordSerializationBinder()
End Class


Public Class ExampleClass
    Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
        Dim serializer As NetDataContractSerializer = New NetDataContractSerializer()

        ' Ensure that Binder is always non-null before deserializing
        serializer.Binder = If(Binders.BookRecord, New Exception("Expected non-null"))

        Using ms As MemoryStream = New MemoryStream(bytes)
            Return CType(serializer.Deserialize(ms), BookRecord)
        End Using
    End Function
End Class

Violation 2

using System;
using System.IO;
using System.Runtime.Serialization;

[DataContract]
public class BookRecord
{
    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Author { get; set; }

    [DataMember]
    public int PageCount { get; set; }

    [DataMember]
    public AisleLocation Location { get; set; }
}

[DataContract]
public class AisleLocation
{
    [DataMember]
    public char Aisle { get; set; }

    [DataMember]
    public byte Shelf { get; set; }
}

public class ExampleClass
{
    public NetDataContractSerializer Serializer { get; set; }

    public BookRecord DeserializeBookRecord(byte[] bytes)
    {
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            return (BookRecord) this.Serializer.Deserialize(ms);    // CA2312 violation
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization

<DataContract()>
Public Class BookRecord
    <DataMember()>
    Public Property Title As String

    <DataMember()>
    Public Property Author As String

    <DataMember()>
    Public Property Location As AisleLocation
End Class

<DataContract()>
Public Class AisleLocation
    <DataMember()>
    Public Property Aisle As Char

    <DataMember()>
    Public Property Shelf As Byte
End Class

Public Class ExampleClass
    Public Property Serializer As NetDataContractSerializer

    Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
        Using ms As MemoryStream = New MemoryStream(bytes)
            Return CType(Me.Serializer.Deserialize(ms), BookRecord)    ' CA2312 violation
        End Using
    End Function
End Class

Solution 2

using System;
using System.IO;
using System.Runtime.Serialization;

public class BookRecordSerializationBinder : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        // One way to discover expected types is through testing deserialization
        // of **valid** data and logging the types used.

        ////Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')");

        if (typeName == "BookRecord")
        {
            return typeof(BookRecord);
        }
        else if (typeName == "AisleLocation")
        {
            return typeof(AisleLocation);
        }
        else
        {
            throw new ArgumentException("Unexpected type", nameof(typeName));
        }
    }
}

[DataContract]
public class BookRecord
{
    [DataMember]
    public string Title { get; set; }

    [DataMember]
    public string Author { get; set; }

    [DataMember]
    public int PageCount { get; set; }

    [DataMember]
    public AisleLocation Location { get; set; }
}

[DataContract]
public class AisleLocation
{
    [DataMember]
    public char Aisle { get; set; }

    [DataMember]
    public byte Shelf { get; set; }
}

public class ExampleClass
{
    public BookRecord DeserializeBookRecord(byte[] bytes)
    {
        NetDataContractSerializer serializer = new NetDataContractSerializer();
        serializer.Binder = new BookRecordSerializationBinder();
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            return (BookRecord) serializer.Deserialize(ms);
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization

Public Class BookRecordSerializationBinder
    Inherits SerializationBinder

    Public Overrides Function BindToType(assemblyName As String, typeName As String) As Type
        ' One way to discover expected types is through testing deserialization
        ' of **valid** data and logging the types used.

        'Console.WriteLine($"BindToType('{assemblyName}', '{typeName}')")

        If typeName = "BinaryFormatterVB.BookRecord" Then
            Return GetType(BookRecord)
        Else If typeName = "BinaryFormatterVB.AisleLocation" Then
            Return GetType(AisleLocation)
        Else
            Throw New ArgumentException("Unexpected type", NameOf(typeName))
        End If
    End Function
End Class

<DataContract()>
Public Class BookRecord
    <DataMember()>
    Public Property Title As String

    <DataMember()>
    Public Property Author As String

    <DataMember()>
    Public Property Location As AisleLocation
End Class

<DataContract()>
Public Class AisleLocation
    <DataMember()>
    Public Property Aisle As Char

    <DataMember()>
    Public Property Shelf As Byte
End Class

Public Class ExampleClass
    Public Function DeserializeBookRecord(bytes As Byte()) As BookRecord
        Dim serializer As NetDataContractSerializer = New NetDataContractSerializer()
        serializer.Binder = New BookRecordSerializationBinder()
        Using ms As MemoryStream = New MemoryStream(bytes)
            Return CType(serializer.Deserialize(ms), BookRecord)
        End Using
    End Function
End Class