Skip to content

VNX-CS-004: C# insecure deserialization: type-embedding formatter or serializer setting

Detects C# deserialisation through BinaryFormatter, SoapFormatter, NetDataContractSerializer, LosFormatter and ObjectStateFormatter, Json.NET TypeNameHandling without a SerializationBinder, JavaScriptSerializer with a type resolver, MessagePack typeless deserialisation, and serializers whose root type comes from Type.GetType on data.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-CS-004
Severity High
SARIF level error
Kind sast
Languages C#
CWE CWE-502
CAPEC CAPEC-586
ATT&CK T1059
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags deserialization csharp
Source internal/sast/rules/vnx-cs-004.rego

Overview

This rule reports the line where data is deserialised by an API that lets the payload choose which .NET types to instantiate: a Deserialize, UnsafeDeserialize or ReadObject call on a BinaryFormatter, SoapFormatter, NetDataContractSerializer, LosFormatter or ObjectStateFormatter (inline, or through a variable, field or parameter, including one typed IFormatter); a Json.NET TypeNameHandling of All, Auto, Objects or Arrays with no SerializationBinder beside it, when the settings are global (SerializerSettings, JsonConvert.DefaultSettings) or the file deserialises; new JavaScriptSerializer(resolver) with a type resolver such as SimpleTypeResolver; and an XmlSerializer or DataContractSerializer built from Type.GetType on a non-constant name. A formatter used only to Serialize, JavaScriptSerializer without a resolver, TypeNameHandling.None, settings only used for writing, and typeof(T) serializers are not reported. These formatters can reconstruct any .NET type present in the loaded assemblies, including types with constructors or property setters that execute code, purely from a byte stream or string provided as input.

The core danger is that deserialisation with these types is effectively a code-execution primitive when given attacker-controlled input. An attacker does not need to find a custom gadget; the .NET runtime class library contains well-documented gadget chains (such as those involving ObjectDataProvider, WindowsIdentity, or TypeConfuseDelegate) that can chain together to call arbitrary methods, including Process.Start or Assembly.Load. These chains are publicly documented and reliably exploitable across many .NET Framework and .NET Core versions.

Microsoft has marked BinaryFormatter obsolete (SYSLIB0011) in .NET 5+ and disabled it by default in .NET 7+. Applications still using these types are carrying a high-severity known-unsafe dependency.

Why This Matters

Insecure deserialization has been in the OWASP Top 10 since 2017 and has produced some of the most severe vulnerabilities in widely used .NET software. CVE-2019-1391 (Microsoft SharePoint), CVE-2020-0688 (Microsoft Exchange), and dozens of Jenkins, Jira, and WebLogic CVEs all exploited unsafe deserialisation to achieve Remote Code Execution without authentication. The attack surface is particularly dangerous in middleware: any application that deserialises objects from HTTP request bodies, cookies, view state, distributed caches, or message queues is potentially reachable by an attacker who has no other foothold on the system.

LosFormatter and ObjectStateFormatter are especially significant because they underpin ASP.NET Web Forms view state. Historically, view state was MAC-protected but not encrypted, and obtaining the machine key (via a path traversal or file read vulnerability) allowed an attacker to forge malicious view state payloads that executed code when the page was processed.

JavaScriptSerializer with a custom JavaScriptTypeResolver can reconstruct arbitrary types from JSON, similar to BinaryFormatter. Even without a type resolver, using these serialisers for untrusted input is considered unsafe in modern .NET security guidance.

What Gets Flagged

// FLAGGED: the Deserialize call on a BinaryFormatter (constructing one to Serialize is not reported)
var formatter = new BinaryFormatter();
var obj = formatter.Deserialize(stream);

// FLAGGED: SoapFormatter.Deserialize called
var sf = new SoapFormatter();
object result = sf.Deserialize(Request.InputStream);

// FLAGGED: NetDataContractSerializer
var serializer = new NetDataContractSerializer();
var data = serializer.Deserialize(inputStream);

// FLAGGED: Json.NET reads $type from the payload
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };

// FLAGGED: JavaScriptSerializer honouring __type
var js = new JavaScriptSerializer(new SimpleTypeResolver());

// FLAGGED: MessagePack typeless API reads a type name from the payload
return MessagePackSerializer.Typeless.Deserialize(payload);

// NOT FLAGGED: no type resolver, fixed root type
var dto = new JavaScriptSerializer().Deserialize<Order>(body);
var xs = new XmlSerializer(typeof(Order));

Remediation

  1. Replace BinaryFormatter with System.Text.Json.JsonSerializer for new code. For existing serialised data that must be migrated, build a one-time migration path that reads the old format once and re-persists using the new serialiser.
  2. Replace SoapFormatter with DataContractSerializer (for existing SOAP contracts) or XmlSerializer with strict schema validation.
  3. For JavaScriptSerializer, migrate to System.Text.Json or Newtonsoft.Json without a type resolver.
  4. Never deserialise data from untrusted sources with any of the flagged types, even if the data is signed or encrypted, signing and encryption protect integrity and confidentiality but do not prevent gadget-chain exploitation if the signature key is ever compromised.
  5. If you must keep BinaryFormatter temporarily for compatibility, add the AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true) switch only behind a feature flag, document the risk, and track removal as a priority remediation item.
// SAFE: System.Text.Json, does not execute arbitrary .NET type constructors
string json = JsonSerializer.Serialize(myObject);
MyType result = JsonSerializer.Deserialize<MyType>(json);

// SAFE: XmlSerializer, serialises only declared members of known types
var xmlSerializer = new XmlSerializer(typeof(MyType));
using var reader = new StringReader(xmlInput);
var result = (MyType)xmlSerializer.Deserialize(reader);

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-cs-004
// nosec vnx-cs-004

Or record an ignore rule: vulnetix ignore add --rule vnx-cs-004 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-cs-004.rego, cli/internal/sast/docs/vnx-cs-004.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.