Legacy code and deserialization

Insecure Deserialization in .NET: Risk and Fixing Legacy Code

Introduction

We have discussed in the previous post regarding the introduction, basically Insecure deserialization is a critical vulnerability that often lurks in legacy systems and internal applications. Serialization and deserialization are foundational operations in modern software development, enabling communication between systems, data storage, and object persistence. However, improper use of deserialization—particularly with unsafe serializers like .NET’s BinaryFormatter—can open the door to severe vulnerabilities, including remote code execution (RCE).

This blog post will unpack the risks associated with insecure deserialization, illustrate real-world vulnerable patterns, and explore multiple remediation strategies. We’ll focus especially on the dangers of the legacy BinaryFormatter class and how developers can safely move away from it.

Vulnerable Scenario: BinaryFormatter in .NET

The BinaryFormatter class in .NET allows for full object graph serialization and deserialization. However, it does not validate input, allowing arbitrary types to be instantiated.

In .NET, this vulnerability is commonly associated with:

  • BinaryFormatter
  • NetDataContractSerializer
  • LosFormatter
  • ObjectStateFormatter

These classes can deserialize almost any type and are highly unsafe when processing untrusted input.

Legacy Code Trap

Many legacy enterprise applications still rely on BinaryFormatter because it was the default serializer in older .NET versions. Replacing it can be time-consuming due to complex type hierarchies and backward compatibility requirements. But leaving it as-is introduces severe security risk, especially if deserialized data comes from untrusted sources.

Let’s look at a basic vulnerable code snippet:

❌ Vulnerable Code
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

public class DeserializeExample
{
    public static object UnsafeDeserialize(byte[] serializedData)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream(serializedData))
        {
            return formatter.Deserialize(ms);
        }
    }
}

🎯 Attack Scenario

An attacker could send a byte stream that includes a malicious gadget chain, which might trigger arbitrary code execution upon deserialization — especially if the application runs with elevated privileges

Why Legacy Code Still Uses It

Many older .NET applications:

  • Were written before deserialization attacks were well known
  • Chose BinaryFormatter for ease of use and object fidelity
  • Are internally hosted and falsely assumed to be secure
  • Still lack CI/CD integration for security linting or SAST scans

This is technical debt, and worse — it’s a security risk.

Secure Alternatives to BinaryFormatter

🔒 Option 1: Replace with System.Text.Json

For simple object serialization:

using System.Text.Json;

public class SecureJsonExample
{
    public static T SafeDeserialize<T>(string json)
    {
        return JsonSerializer.Deserialize<T>(json);
    }
}
  • JSON serialization doesn’t allow arbitrary types or code execution
  • Works well for DTOs and data models
🔒 Option 2: Use XmlSerializer with Explicit Types
using System.Xml.Serialization;

public class XmlExample
{
    public static MyObject SafeDeserialize(string xml)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
        using (StringReader reader = new StringReader(xml))
        {
            return (MyObject)serializer.Deserialize(reader);
        }
    }
}

✅ Secure, as long as you don’t allow DTD processing or external entities (XXE).

🔒 Option 3: Use Custom Binders (with BinaryFormatter – still discouraged)

If you must use BinaryFormatter due to legacy dependencies:

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

public class SecureDeserializeWithBinder
{
    public static object Deserialize(byte[] data)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Binder = new SafeSerializationBinder();

        using (var ms = new MemoryStream(data))
        {
            return formatter.Deserialize(ms);
        }
    }

    sealed class SafeSerializationBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            if (typeName != "MyApp.Models.MySafeType")
                throw new SerializationException("Type not allowed for deserialization.");

            return typeof(MyApp.Models.MySafeType);
        }
    }
}

But note: even with binders, BinaryFormatter is deprecated as of .NET 5 and removed in .NET 7.

Real-World Examples

🎯 Exploiting a vulnerable API

Consider an internal API that accepts serialized data as a parameter. If it blindly deserializes the content using BinaryFormatter, an attacker could upload a payload like:

ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -o raw -c "calc.exe" > payload.bin

When this payload is deserialized, calc.exe or worse can execute on the server.

🛡️ General Mitigation Strategies

MitigationDescription
Replace BinaryFormatterUse safer serializers (System.Text.Json, XmlSerializer, etc.)
Apply Allow ListsIf forced to use deserialization, restrict deserialization only to expected types
Disable Dangerous APIsRemove endpoints or methods that allow arbitrary data deserialization
Code AuditsUse tools like SAST (Static Application Security Testing) to find unsafe usage
Network ProtectionsUse WAFs and input sanitization to detect unusual serialized payloads
Logging and MonitoringAlert on deserialization exceptions or unusual object graph patterns

Testing and Detection

Tools:
  • YSOSerial.NET – for payload generation
  • SAST tools – SonarQube, Checkmarx, Fortify
  • Runtime Protection – Use .NET logging and Application Insights to detect anomalies

The End of BinaryFormatter

As of .NET 5 and beyond, Microsoft has officially deprecated BinaryFormatter.

⚠️ “BinaryFormatter is insecure and shouldn’t be used. Use safer alternatives like System.Text.Json.” — Microsoft Docs

Migration is not just a security recommendation — it’s a platform requirement for future compatibility.

Key Recommendations from Microsoft

According to Microsoft’s BinaryFormatter Security Guide:

  • Never use BinaryFormatter on untrusted data
  • Avoid using NetDataContractSerializer and LosFormatter too — they have similar risks.
  • Use System.Text.Json, DataContractSerializer, or XmlSerializer wherever possible.
  • Upgrade older libraries and frameworks to remove BinaryFormatter usage.
  • Use static code analysis tools (like Roslyn analyzers) to detect insecure deserialization automatically.

Conclusion

Insecure deserialization is an invisible, yet potent, security risk — especially in legacy systems that use powerful but unsafe serializers like BinaryFormatter. Replacing it takes work, but the cost of not doing so could be unauthorized access, system compromise, or regulatory fines.

Reference

  1. BinaryFormatter Class :- https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter?view=net-9.0
  2. Deserialization risks in use of BinaryFormatter and related types : https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-security-guide

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *