Don't use insecure deserializers

Rule ND3110 Security

Matches methods using a deserializer that instantiates arbitrary types named in the payload: BinaryFormatter (obsolete SYSLIB0011, removed from .NET 9), SoapFormatter, NetDataContractSerializer, LosFormatter, ObjectStateFormatter, JavaScriptSerializer with a type resolver, or Newtonsoft.Json with TypeNameHandling set. Gadget chains available in the framework turn such deserialization of untrusted input into remote code execution: https://learn.microsoft.com/dotnet/standard/serialization/binaryformatter-security-guide

This is CWE-502 Deserialization of Untrusted Data.

Flagged Code
static Order Load(byte[] payload) {
   var formatter = new BinaryFormatter();                       // flagged: BinaryFormatter
   using var stream = new MemoryStream(payload);
   return (Order)formatter.Deserialize(stream);
}
static JsonSerializerSettings Settings() =>
   new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };   // flagged
Fixed Code
static Order Load(byte[] payload) =>
   JsonSerializer.Deserialize<Order>(payload);                  // System.Text.Json, fixed type
static JsonSerializerSettings Settings() =>
   new JsonSerializerSettings();                                // TypeNameHandling stays None

How to fix

Use System.Text.Json, XmlSerializer or DataContractSerializer with a fixed, known set of types. With Newtonsoft.Json keep TypeNameHandling.None or provide a restrictive SerializationBinder. If a legacy format must be read, deserialize it only from trusted sources and suppress the issue with a justification.

CQLinq source

This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.

// <Name>Don't use insecure deserializers</Name>
// <Id>ND3110:DontUseInsecureDeserializers</Id>
warnif count > 0

// Third-party members are part of the code model only when the application uses them.
let insecureDeserializerTypes = ThirdParty.Types.WithFullNameIn(
  "System.Runtime.Serialization.Formatters.Binary.BinaryFormatter",
  "System.Runtime.Serialization.Formatters.Soap.SoapFormatter",
  "System.Runtime.Serialization.NetDataContractSerializer",
  "System.Web.UI.LosFormatter",
  "System.Web.UI.ObjectStateFormatter",
  "System.Messaging.BinaryMessageFormatter",
  // JavaScriptSerializer becomes unsafe as soon as a type resolver is provided
  "System.Web.Script.Serialization.SimpleTypeResolver",
  "System.Web.Script.Serialization.JavaScriptTypeResolver").ToHashSetEx()

// Newtonsoft.Json: TypeNameHandling other than None lets the payload choose the types to instantiate.
// The enum value is not visible in IL, but explicitly setting the property is the smell to review.
let typeNameHandlingSetters = ThirdParty.Types.WithFullNameIn(
  "Newtonsoft.Json.JsonSerializerSettings",
  "Newtonsoft.Json.JsonSerializer")
  .ChildMethods().Where(m => m.SimpleName == "set_TypeNameHandling").ToHashSetEx()

where insecureDeserializerTypes.Any() || typeNameHandlingSetters.Any()

from m in Application.Methods
let insecureTypesUsed = insecureDeserializerTypes.Where(t => m.IsUsing(t)).ToArray()
let typeNameHandlingSettersCalled = m.MethodsCalled.Intersect(typeNameHandlingSetters).ToArray()
where insecureTypesUsed.Length > 0 || typeNameHandlingSettersCalled.Length > 0
select new {
  m,
  insecureTypesUsed,
  typeNameHandlingSettersCalled,
  Debt = (insecureTypesUsed.Length > 0 ? 2.ToHours() : 30.ToMinutes()).ToDebt(),
  Severity = insecureTypesUsed.Length > 0 ? Severity.Critical : Severity.High
}

//<Expl>
// The *{0}* uses {1}{2}: deserializing untrusted data with it can lead to
// *remote code execution*, since the payload decides which types get instantiated.
//</Expl>

//<Desc>
// Matches methods using a deserializer that instantiates arbitrary types
// named in the payload: *BinaryFormatter* (obsolete SYSLIB0011, removed
// from .NET 9), *SoapFormatter*, *NetDataContractSerializer*,
// *LosFormatter*, *ObjectStateFormatter*, *JavaScriptSerializer* with a
// type resolver, or Newtonsoft.Json with *TypeNameHandling* set.
// Gadget chains available in the framework turn such deserialization of
// untrusted input into remote code execution:
// https://learn.microsoft.com/dotnet/standard/serialization/binaryformatter-security-guide
//
// This is CWE-502 *Deserialization of Untrusted Data*.
//
// **Flagged Code**
// ```csharp
// static Order Load(byte[] payload) {
//    var formatter = new BinaryFormatter();                       // flagged: BinaryFormatter
//    using var stream = new MemoryStream(payload);
//    return (Order)formatter.Deserialize(stream);
// }
// static JsonSerializerSettings Settings() =>
//    new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };   // flagged
// ```
//
// **Fixed Code**
// ```csharp
// static Order Load(byte[] payload) =>
//    JsonSerializer.Deserialize<Order>(payload);                  // System.Text.Json, fixed type
// static JsonSerializerSettings Settings() =>
//    new JsonSerializerSettings();                                // TypeNameHandling stays None
// ```
//</Desc>

//<HowToFix>
// Use *System.Text.Json*, *XmlSerializer* or *DataContractSerializer*
// with a fixed, known set of types. With Newtonsoft.Json keep
// *TypeNameHandling.None* or provide a restrictive *SerializationBinder*.
// If a legacy format must be read, deserialize it only from trusted
// sources and suppress the issue with a justification.
//</HowToFix>

Security: all 39 items