Deserialization can be dangerous
This post is part of the series 'Vulnerabilities'. Be sure to check out the rest of the blog posts of the series!
- Impersonation and security
- SQL injections
- How to prevent CSRF attacks
- ASP MVC and XSRF
- Cross-site scripting (XSS)
- ASP MVC: Mass Assignment
- Regex - Deny of Service (ReDoS)
- Deserialization can be dangerous (this post)
- Prevent Zip bombs in a .NET application
- Prevent Zip Slip in .NET
- How to protect against XML vulnerabilities in .NET
Serialization is the process of converting an object into a stream of bytes to store the object or transmit it to memory, a database, or a file. It is very handy to store data and load them later or to transfer data between 2 systems. The .NET frameworks comes with multiple serializers including BinaryFormatter
, JavaScriptSerializer
, XmlSerializer
, DataContractSerializer
. While it is easy to serialize and deserialize data, you should take care of the options provided by the deserializer to not introduce a vulnerability in your code.
For instance, you can use the BinarySerializer
to persist an object to a file.
static void Main(string[] args)
{
var o = new Sample { Path = "test" };
using (var stream = File.OpenWrite("output.bin"))
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(stream, collection);
}
using (var stream = File.OpenRead("output.bin"))
{
var binaryFormatter = new BinaryFormatter();
var deserialized = binaryFormatter.Deserialize(stream);
}
}
[Serializable]
class Sample
{
public string Path { get; set; }
}
#Why is it dangerous?
However, the deserializer does not validate that the file has not been tampered. If you send a file to a server that contains a binary serialized data, you can create an instance of any type on the server. The deserializer will instantiate the object and assign the value of its properties. This code is an issue if the constructor and the properties can execute some undesirable code. The second way to execute code is to instantiate a class that implements IDisposable
such as TempFileCollection
. This class deletes the registered files on Dispose. So, once the GC calls the finalizer the files will be deleted. But you could do something more terrifying and maybe run any command on the machine. That's what is called Remote Code Execution (RCE). The binary serializer was the cause of multiple real-life vulnerabilities such as in Microsoft Exchange Server (CVE-2018-8302) and more recently in Docker (CVE-2018-15514).
To sum up, deserialization executes code from different locations:
- The constructor of each deserialized instance
- The getter/setter of each property
- The destructor when the garbage collection (GC) destroy the instance
#how to prevent that attack with common .NET serializers?
You can prevent this by implementing a custom SerializationBinder
to only allow a reduced set of type to be instantiated:
using (var stream = File.OpenRead("output.bin"))
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Binder = new CustomBinder();
var deserialized = binaryFormatter.Deserialize(stream);
}
// Only allow known-types to be instantiated
class CustomBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (assemblyName == typeof(Sample).Assembly.FullName && typeName == typeof(Sample).FullName)
return typeof(Sample);
throw new ArgumentOutOfRangeException();
}
}
If you are using ASP.NET, you may know that the ViewState
can store any serializable object. This means it can also deserialize these objects. ASP.NET uses the BinarySerializer
to store the ViewState
. So, a malicious payload may be able to create an instance of any type on your server. You can prevent this by setting enableViewStateMac
to true (on by default). This will first check that the payload has been generated by the server before deserializing it. You can read more about this parameter on the ASP.NET blog.
Other serializers may also be sensible to this attack. For instance, if you misconfigure Json.NET you may be vulnerable:
var settings = new JsonSerializerSettings
{
// Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions
// from deserialization errors that might occur from deeply nested objects.
MaxDepth = 32,
// Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types
TypeNameHandling = TypeNameHandling.None,
};
JsonConvert.DeserializeObject<T>(jsonString, settings);
JavaScriptSerializer
is also vulnerable if you do not set a type resolver:
var resolver = new CustomJavaScriptTypeResolver();
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(resolver);
serializer.RecursionLimit = 32;
#More generally, how to prevent that attack?
- When you roundtrip state to a user-controlled space, sign the data. Thus, you can validate that the data has been generated by your application and it should not contain any malicious data.
- Don't publish the signing key, so no one can generate the malicious payload.
- Don't use any serialization format that allows an attacker to specify the object type to be deserialized. It may require some configuration for some serializers.
Do you have a question or a suggestion about this post? Contact me!