Security: Deserialization
-
Deserializing data can trigger the execution of gadgets that may initiate RCE (Remote Code Execution) attacks on the client machine.
-
To handle this threat, RavenDB's default deserializer blocks the deserialization of known .NET RCE gadgets.
-
Users can customize the list of namespaces and types for which deserialization is forbidden or allowed.
-
In this article:
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.VisualStudionamespace and for the following known .NET RCE gadgets:System.Configuration.Install.AssemblyInstallerSystem.Activities.Presentation.WorkflowDesignerSystem.Windows.ResourceDictionarySystem.Windows.Data.ObjectDataProviderSystem.Windows.Forms.BindingSourceMicrosoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProviderSystem.Data.DataViewManagerSystem.Xml.XmlDocumentSystem.Xml.XmlDataDocumentSystem.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)
| Parameter | Type | Description |
|---|---|---|
| @namespace | string | The 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)
| Parameter | Type | Description |
|---|---|---|
| type | Type | The 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)
| Parameter | Type | Description |
|---|---|---|
| type | Type | The 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
}
}
};