Second Breakfast: Implicit and Mutation-Based Serialization Vulnerabilities in .NET
This talk demonstrates how implicit and mutation-based deserialization vulnerabilities in .NET NoSQL engines can lead to Remote Code Execution (RCE). The research highlights how attackers can manipulate serialized data structures, such as dictionaries and hash tables, to force the instantiation of dangerous .NET gadgets like AssemblyInstaller and ObjectDataProvider. The presentation provides a comprehensive analysis of how these vulnerabilities affect popular NoSQL databases and explains why common defenses like serialization binders are often insufficient or bypassable. The speaker concludes with best practices for secure serialization and warns against the inherent risks of polymorphic deserialization.
Exploiting .NET NoSQL Engines Through Implicit and Mutation-Based Deserialization
TLDR: Modern .NET NoSQL engines often rely on insecure polymorphic deserialization, allowing attackers to achieve Remote Code Execution (RCE) by injecting malicious type information into stored data. This research highlights how common defenses like serialization binders are frequently bypassed using generic type manipulation and property contagion. Security researchers and developers should audit their data storage layers for insecure deserialization patterns and avoid polymorphic serializers entirely.
Deserialization vulnerabilities are far from dead. While many developers have moved away from the most egregious examples of insecure object handling, the rise of NoSQL databases in the .NET ecosystem has introduced a new, often overlooked attack surface. Many of these engines, including LiteDB, MongoDB, and RavenDB, rely on internal serializers that implicitly trust type information provided within the data stream. If an attacker can influence the data stored in these databases, they can force the application to instantiate arbitrary .NET types, leading to full system compromise.
The Mechanics of Implicit Deserialization
At the heart of this issue is the use of polymorphic serializers. These tools are designed to handle complex object graphs by embedding type metadata directly into the serialized output. When the application reads this data back, it uses that metadata to determine which class to instantiate. The problem arises when the application does not validate this type information against a strict allow-list.
In the case of LiteDB, the library uses a BSON mapper that includes an _type field in its output. An attacker who can inject or modify a BSON document can change this field to point to a dangerous gadget class. By setting the _type to something like System.Configuration.Install.AssemblyInstaller, the attacker can force the application to load a malicious DLL when the object is deserialized. This is a classic Software and Data Integrity Failure, where the application blindly trusts the integrity of the data it retrieves from its own storage.
The following payload demonstrates how an attacker might target an application using LiteDB to launch a local process:
{
"_type": "System.Configuration.Install.AssemblyInstaller, System.Configuration.Install",
"Path": "C:\\temp\\malicious.dll"
}
When the application calls the deserializer on this object, the AssemblyInstaller is instantiated, and its Path property is set to the attacker-controlled DLL. The framework then attempts to load the assembly, executing the attacker's code in the process.
Mutation-Based Attacks and Serialization Binders
Many developers attempt to mitigate these risks by implementing a SerializationBinder. The goal is to create a filter that only allows specific, safe types to be deserialized. However, these implementations are frequently flawed. A common mistake is to allow-list an entire assembly, assuming that all types within it are safe. This is a dangerous assumption.
Attackers can bypass these weak binders using a technique called mutation. If an application allows the deserialization of a generic collection like a Dictionary<string, string>, an attacker can manipulate the dictionary keys to include the _type metadata. Because the binder is only asked to validate the dictionary type itself, it often ignores the malicious type information hidden within the dictionary's contents.
Furthermore, the concept of contagion allows attackers to chain these vulnerabilities. If a safe, allow-listed type contains a property that accepts a more complex or dangerous type, the attacker can target that nested object. The binder might approve the parent object, but the deserializer will continue to process the nested, malicious object without further scrutiny. This effectively renders the binder useless, as the attacker can reach the dangerous gadget through a legitimate, approved path.
Real-World Impact for Pentesters
During a penetration test or a bug bounty engagement, you should look for any application that stores user-controlled data in a NoSQL database and subsequently reads it back into a complex object. If you can influence the data, you have a potential RCE vector.
Start by inspecting the database files or the network traffic for any signs of serialized objects. Look for keys like _type, _t, or other indicators of polymorphic serialization. If you find them, attempt to swap the type name with a known .NET gadget. The ObjectDataProvider is a perennial favorite for these attacks because it allows for the invocation of arbitrary static methods.
The impact of these vulnerabilities is critical. Because the deserialization often occurs with the privileges of the application process, an attacker can gain full control over the underlying server. This is not just a theoretical risk; it is a direct path to code execution that bypasses many traditional perimeter defenses.
Defensive Strategies
Defending against these attacks requires a fundamental shift in how applications handle data. The most effective defense is to avoid polymorphic serialization entirely. If you must use it, ensure that your SerializationBinder is as restrictive as possible. Do not allow-list entire assemblies. Instead, explicitly list every single type that your application expects to deserialize.
If you are using Json.NET, be aware of the TypeNameHandling setting. Setting this to All or Auto is a recipe for disaster. Keep it set to None whenever possible. For those who need to store complex data, consider using a format that does not include type information, such as standard JSON or a schema-based format like Protocol Buffers.
Ultimately, the responsibility lies with the developer to ensure that the data layer is not a source of untrusted input. If your application treats data retrieved from a database as a trusted source, you are already vulnerable. Treat all data, whether it comes from a user or a database, as potentially malicious. Audit your dependencies, understand how they handle serialization, and design your object models to be resilient against type-based manipulation. The goal is to make the application logic independent of the data structure, ensuring that even if an attacker can control the data, they cannot control the execution flow.
Vulnerability Classes
Tools Used
Target Technologies
Attack Techniques
OWASP Categories
All Tags
Up Next From This Conference

Chained to Hit: Discovering New Vectors to Gain Remote and Root Access in SAP Enterprise Software

Zero-Touch-Pwn: Abusing Zoom's Zero Touch Provisioning for Remote Attacks on Desk Phones

ODDFuzz: Hunting Java Deserialization Gadget Chains via Structure-Aware Directed Greybox Fuzzing
Similar Talks

Kill List: Hacking an Assassination Site on the Dark Web

Firewalls Under Fire: China's Ongoing Campaign to Compromise Network Protection Devices

