Skip to main content

Security: Deserialization

Securing deserialization

The deserialization risk

  • When a RavenDB client uses the Newtonsoft library to deserialize a JSON string to a .NET object, the object may include a reference to a gadget (a code segment) and the deserialization process may execute it.

  • Some gadgets attempt to exploit the deserialization process and initiate an RCE (Remote Code Execution) attack that may, for example, inject the system with malicious code, steal information, or take control of the machine.

  • To prevent such exploitation, RavenDB's default deserializer blocks deserialization for the Microsoft.VisualStudio namespace and for the following known .NET RCE gadgets:

    • System.Configuration.Install.AssemblyInstaller
    • System.Activities.Presentation.WorkflowDesigner
    • System.Windows.ResourceDictionary
    • System.Windows.Data.ObjectDataProvider
    • System.Windows.Forms.BindingSource
    • Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider
    • System.Data.DataViewManager
    • System.Xml.XmlDocument
    • System.Xml.XmlDataDocument
    • System.Management.Automation.PSObject
  • Users can customize the list of namespaces and types for which deserialization is forbidden or allowed.


Direct vs. indirect gadget loading

Directly-loaded gadgets cannot be blocked using DefaultRavenSerializationBinder.
When a gadget is loaded directly, its execution during deserialization is permitted regardless of the binder's forbidden list.

For example, the following Load call will succeed regardless of the binder configuration:

// The object will be allowed to be deserialized
// regardless of the binder list.
session.Load<object>("Gadget");

Indirectly-loaded gadgets can be blocked using DefaultRavenSerializationBinder.
When a gadget type name is embedded as a value inside a JSON string, it is resolved only at deserialization time. At that point the binder can intercept and block it.

For example, in the following payload, the type is not loaded directly but appears as a JSON value and is resolved only during deserialization:

{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"$values": ["cmd", "/c calc.exe"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
}
}

Including System.Windows.Data.ObjectDataProvider in the forbidden list prevents this payload from being deserialized and executed.

DefaultRavenSerializationBinder

Use DefaultRavenSerializationBinder and its methods to block the deserialization of suspicious namespaces and types, or to explicitly allow the deserialization of trusted types.

Create a new DefaultRavenSerializationBinder instance, call the registration methods to define your overrides, then configure the instance as a serialization convention as shown in the example.

All Register* methods must be called before the binder processes its first type.
Calling any registration method after the binder has been used throws:
InvalidOperationException: "Cannot perform this operation, because binder was already used."

Note that DefaultRavenSerializationBinder.Instance is a shared static singleton.
Always create your own new DefaultRavenSerializationBinder() instance; do not call registration methods on the static instance.

RegisterForbiddenNamespace

Use RegisterForbiddenNamespace to prevent the deserialization of any type belonging to the given namespace.

public void RegisterForbiddenNamespace(string @namespace)

ParameterTypeDescription
@namespacestringThe namespace from which deserialization will be blocked.

Attempting to deserialize a type whose namespace is forbidden throws:
InvalidOperationException: "Cannot resolve type '<FullName>' because the namespace is on a blacklist due to security reasons. Please customize json deserializer in the conventions and override SerializationBinder with your own logic if you want to allow this type."

If the forbidden type appears as a generic argument (e.g. List<System.Xml.XmlDocument>), a containing exception is thrown, with the original wrapped as its inner exception:
InvalidOperationException: "Generic type: <FullName>, contains a generic argument <FullName> that is blocked from serialization"


RegisterForbiddenType

Use RegisterForbiddenType to prevent the deserialization of a specific type.

public void RegisterForbiddenType(Type type)

ParameterTypeDescription
typeTypeThe type whose deserialization will be blocked.

Attempting to deserialize a forbidden type throws:
InvalidOperationException: "Cannot resolve type '<FullName>' because the type is on a blacklist due to security reasons. Please customize json deserializer in the conventions and override SerializationBinder with your own logic if you want to allow this type."

If the forbidden type appears as a generic argument (e.g. List<SuspiciousClass>), a containing exception is thrown, with the original wrapped as its inner exception:
InvalidOperationException: "Generic type: <FullName>, contains a generic argument <FullName> that is blocked from serialization"


RegisterSafeType

Use RegisterSafeType to explicitly allow the deserialization of a specific type.
A type registered as safe bypasses all restrictions, including user-registered forbidden namespaces, user-registered forbidden types, and the built-in hardcoded forbidden-types list.

public void RegisterSafeType(Type type)

ParameterTypeDescription
typeTypeThe type whose deserialization will be permitted.

Example

The following example creates a DefaultRavenSerializationBinder instance, registers a forbidden namespace, a forbidden type, and a trusted type, then applies it to the document store:

// Create a new deserialization binder instance
var binder = new DefaultRavenSerializationBinder();

// Block all types in this namespace
binder.RegisterForbiddenNamespace("SuspiciousNamespace");

// Block a specific type
binder.RegisterForbiddenType(typeof(SuspiciousClass));

// Explicitly allow a specific type (overrides all restrictions)
binder.RegisterSafeType(typeof(TrustedClass));

var store = new DocumentStore
{
Conventions =
{
Serialization = new NewtonsoftJsonSerializationConventions
{
// Apply the binder to the deserializer
CustomizeJsonDeserializer = deserializer =>
deserializer.SerializationBinder = binder
}
}
};

In this article