다음을 통해 공유


CA2302: BinaryFormatter.Deserialize를 호출하기 전에 BinaryFormatter.Binder가 설정되어 있는지 확인합니다.

속성
규칙 ID CA2302
제목 BinaryFormatter.Deserialize를 호출하기 전에 BinaryFormatter.Binder가 설정되었는지 확인합니다.
범주 보안
수정 사항이 주요 변경인지 여부 주요 변경 아님
.NET 9에서 기본적으로 사용 아니요

원인

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialization 메서드가 호출되거나 참조되었으며 Binder 속성이 null일 수 있습니다.

이 규칙은 CA2301과 비슷하지만, 분석에서 Binder가 명확하게 null인지 확인할 수 없습니다.

기본적으로 이 규칙은 전체 코드베이스를 분석하지만 이는 구성 가능합니다.

Warning

SerializationBinder를 사용하여 형식을 제한하면 일부 공격을 방지할 수 없습니다. 자세한 내용은 BinaryFormatter 보안 가이드를 참조하세요.

규칙 설명

안전하지 않은 역직렬 변환기는 신뢰할 수 없는 데이터를 역직렬화할 때 취약합니다. 공격자는 예기치 않은 형식을 포함하도록 직렬화된 데이터를 수정하여 악의적인 부작용을 개체에 주입할 수 있습니다. 예를 들어 안전하지 않은 역직렬 변환기에 대한 공격은 기본 운영 체제에서 명령을 실행하거나, 네트워크를 통해 통신하거나, 파일을 삭제할 수 있습니다.

이 규칙은 Binder가 null일 수 있는 경우 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialization 메서드 호출 또는 참조를 찾습니다. Binder 속성과 관계없이 BinaryFormatter를 사용하여 deserialization을 허용하지 않으려면 이 규칙 및 CA2301을 사용하지 않도록 설정하고 CA2300 규칙을 사용하도록 설정합니다.

위반 문제를 해결하는 방법

  • 보안 직렬 변환기를 대신 사용하고 공격자가 역직렬화할 임의 형식을 지정할 수 없도록 합니다. 자세한 내용은 기본 설정 대안을 참조하세요.
  • 직렬화된 데이터를 변조 방지로 설정합니다. serialization 후에 직렬화된 데이터에 암호화된 방식으로 서명합니다. deserialization 전에 암호화 시그니처의 유효성을 검사합니다. 암호화 키가 공개되지 않도록 보호하고 키 순환을 설계합니다.
  • 이 옵션을 사용하면 코드가 서비스 거부 공격에 취약해지고 향후 원격 코드 실행 공격이 발생할 수 있습니다. 자세한 내용은 BinaryFormatter 보안 가이드를 참조하세요. 역직렬화된 형식을 제한합니다. 사용자 지정 System.Runtime.Serialization.SerializationBinder를 구현합니다. 역직렬화하기 전에 모든 코드 경로에서 Binder 속성을 사용자 지정 SerializationBinder 인스턴스로 설정합니다. 재정의된 BindToType 메서드에서 형식이 예기치 않은 형식인 경우 예외를 throw하여 deserialization을 중지합니다.

경고를 표시하지 않는 경우

BinaryFormatter는 안전하지 않으며 안전하게 할 수 없습니다.

분석할 코드 구성

다음 옵션을 사용하여 이 규칙이 실행될 코드베이스 부분을 구성합니다.

이 규칙, 적용되는 모든 규칙 또는 적용되는 이 범주(보안)의 모든 규칙에 대해 이러한 옵션을 구성할 수 있습니다. 자세한 내용은 코드 품질 규칙 구성 옵션을 참조하세요.

특정 기호 제외

분석에서 형식 및 메서드와 같은 특정 기호를 제외할 수 있습니다. 예를 들어 MyType이라는 형식 내 코드에서 규칙을 실행하지 않도록 지정하려면 프로젝트의 .editorconfig 파일에 다음 키-값 쌍을 추가합니다.

dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType

옵션 값의 허용되는 기호 이름 형식(|로 구분):

  • 기호 이름만(포함하는 형식 또는 네임스페이스와 관계없이 해당 이름의 모든 기호 포함).
  • 기호의 설명서 ID 형식에 있는 정규화된 이름. 각 기호 이름에는 메서드의 경우 M:, 형식의 경우 T:, 네임스페이스의 경우 N:과 같은 기호 종류 접두사가 필요합니다.
  • 생성자의 경우 .ctor이고 정적 생성자의 경우 .cctor입니다.

예:

옵션 값 요약
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType MyType이라는 모든 기호와 일치합니다.
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType1|MyType2 MyType1 또는 MyType2라는 모든 기호와 일치합니다.
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) 특정 메서드 MyMethod를 지정된 정규화된 시그니처와 비교합니다.
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) 특정 메서드 MyMethod1MyMethod2를 개별 정규화된 시그니처와 비교합니다.

특정 형식 및 해당 파생 형식 제외

분석에서 특정 형식과 해당 파생 형식을 제외할 수 있습니다. 예를 들어 MyType이라는 형식 및 해당 파생 형식 내에 있는 메서드에서 규칙이 실행되지 않도록 지정하려면 프로젝트의 .editorconfig 파일에 다음 키-값 쌍을 추가합니다.

dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType

옵션 값의 허용되는 기호 이름 형식(|로 구분):

  • 형식 이름만(포함하는 형식이나 네임스페이스와 관계없이 해당 이름의 모든 형식 포함)
  • 기호의 설명서 ID 형식에 있는 정규화된 이름(선택적 T: 접두사 포함)

예:

옵션 값 요약
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType MyType이라는 모든 형식 및 모든 해당 파생 형식과 일치합니다.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType1|MyType2 MyType1 또는 MyType2라는 모든 형식 및 모든 해당 파생 형식과 일치합니다.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS.MyType 지정된 정규화된 이름의 특정 MyType 형식 및 모든 해당 파생 형식과 일치합니다.
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS1.MyType1|M:NS2.MyType2 개별 정규화된 이름의 특정 MyType1, MyType2 형식 및 모든 해당 파생 형식과 일치합니다.

의사 코드 예제

위반 1

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

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));
        }
    }
}

[Serializable]
public class BookRecord
{
    public string Title { get; set; }
    public AisleLocation Location { get; set; }
}

[Serializable]
public class AisleLocation
{
    public char Aisle { get; set; }
    public byte Shelf { get; set; }
}

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

public class ExampleClass
{
    public BookRecord DeserializeBookRecord(byte[] bytes)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Binder = Binders.BookRecord;
        using (MemoryStream ms = new MemoryStream(bytes))
        {
            return (BookRecord)formatter.Deserialize(ms);    // CA2302 violation
        }
    }
}
Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Binary

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

<Serializable()>
Public Class BookRecord
    Public Property Title As String
    Public Property Location As AisleLocation
End Class

<Serializable()>
Public Class AisleLocation
    Public Property Aisle As Char
    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 formatter As BinaryFormatter = New BinaryFormatter()
        formatter.Binder = Binders.BookRecord
        Using ms As MemoryStream = New MemoryStream(bytes)
            Return CType(formatter.Deserialize(ms), BookRecord)    ' CA2302 violation
        End Using
    End Function
End Class

솔루션 1

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

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));
        }
    }
}

[Serializable]
public class BookRecord
{
    public string Title { get; set; }
    public AisleLocation Location { get; set; }
}

[Serializable]
public class AisleLocation
{
    public char Aisle { get; set; }
    public byte Shelf { get; set; }
}

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

public class ExampleClass
{
    public BookRecord DeserializeBookRecord(byte[] bytes)
    {
        BinaryFormatter formatter = new BinaryFormatter();

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

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

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

<Serializable()>
Public Class BookRecord
    Public Property Title As String
    Public Property Location As AisleLocation
End Class

<Serializable()>
Public Class AisleLocation
    Public Property Aisle As Char
    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 formatter As BinaryFormatter = New BinaryFormatter()

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

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

위반 2

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
public class BookRecord
{
    public string Title { get; set; }
    public AisleLocation Location { get; set; }
}

[Serializable]
public class AisleLocation
{
    public char Aisle { get; set; }
    public byte Shelf { get; set; }
}

public class ExampleClass
{
    public BinaryFormatter Formatter { get; set; }

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

<Serializable()>
Public Class BookRecord
    Public Property Title As String
    Public Property Location As AisleLocation
End Class

<Serializable()>
Public Class AisleLocation
    Public Property Aisle As Char
    Public Property Shelf As Byte
End Class

Public Class ExampleClass
    Public Property Formatter As BinaryFormatter

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

해결 방법 2

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

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));
        }
    }
}

[Serializable]
public class BookRecord
{
    public string Title { get; set; }
    public AisleLocation Location { get; set; }
}

[Serializable]
public class AisleLocation
{
    public char Aisle { get; set; }
    public byte Shelf { get; set; }
}

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

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

<Serializable()>
Public Class BookRecord
    Public Property Title As String
    Public Property Location As AisleLocation
End Class

<Serializable()>
Public Class AisleLocation
    Public Property Aisle As Char
    Public Property Shelf As Byte
End Class

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