Deserialization with a formatter that reconstructs the type it names

ID

csharp.insecure_deserialization

Severity

low

Remediation Complexity

medium

Remediation Risk

medium

Remediation Effort

medium

Resource

Misconfiguration

Language

CSharp

Tags

ASVS50:v1.5.2, CWE:502, NIST.SP.800-53, OWASP:2025:A08, PCI-DSS:6.5.1

Description

A payload is read with a formatter that reconstructs whatever type the payload names, or a serializer is configured to take the type from the payload. The payload then decides which classes are instantiated and what runs while they are built.

Rationale

csharp.code_injection_deserialization reports deserialization when a data path can be followed from an external input to the read. That needs the input to be visible: a BinaryFormatter.Deserialize in a codec or helper class, where the caller lives elsewhere, has no source to follow and is reported by nothing.

Its severity is low because the finding says nothing about whether an attacker-controlled payload actually reaches the call — when that can be shown, csharp.code_injection_deserialization reports it as critical, and this finding is dropped as a duplicate.

What is reported

The type-carrying formatters, on Deserialize / UnsafeDeserialize / ReadObject: BinaryFormatter, NetDataContractSerializer, SoapFormatter, LosFormatter, ObjectStateFormatter. Each reconstructs whatever type the payload names and each is documented as unsafe for untrusted input.

Newtonsoft’s TypeNameHandling set to anything but None — All, Objects, Arrays, Auto — is reported where it is configured, in an object initializer or a property assignment. That single setting is both the defect and its fix, and the settings object it produces is normally used elsewhere.

BinaryFormatter is obsolete from .NET 5 and gated behind an opt-in switch from .NET 8, yet the type is still present and instantiable on .NET 10 (verified) and a great deal of code targets .NET Framework. It stays reported: it is an explicit choice on any target, which is what this rule looks for. No sink here rests on a library default.

What counts as a restriction

A serialization binder installed on the same receiver in the enclosing method — Binder or SerializationBinder — is the documented way to restrict what a formatter may reconstruct, and suppresses the finding. TypeNameHandling.None is not reported, being the safe value and the default.

Examples

Reported:

var formatter = new BinaryFormatter();
return formatter.Deserialize(payload);          // reported
return new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };   // reported

Not reported — a binder restricts the types:

var formatter = new BinaryFormatter();
formatter.Binder = new AllowlistBinder();
return formatter.Deserialize(payload);

Remediation

Carry the data in a format that names no types. DataContractSerializer or System.Text.Json against a fixed type gives the same convenience without letting the payload choose the class:

var serializer = new DataContractSerializer(typeof(Order));
return (Order) serializer.ReadObject(payload);

For Newtonsoft, leave TypeNameHandling at None. Where polymorphism is genuinely needed, pair it with a SerializationBinder that allowlists the accepted types, and treat that allowlist as security-relevant code.

Limitations

The receiver of a binder assignment is matched by the text it was written as, within the enclosing method, so a formatter restricted across method boundaries or through a helper is not correlated and is still reported.

© 2026 Xygeni. All rights reserved