Link Search Menu Expand Document

Unsafe Deserialization in .NET

Vulnerable Example

The .NET framework offers several instances of deserialization. Developers will likely be familiar with the following example, where some untrusted binary data is deserialized to create some objects:

[Serializable]
public class SomeClass
{
	public string SomeProperty { get; set; }
	public double SomeOtherProperty { get; set; }
}

class Program
{
	static void Main(string[] args)
	{
	   BinaryFormatter binaryFormatter = new BinaryFormatter();
	   MemoryStream memoryStream = new MemoryStream(File.ReadAllBytes("untrusted.file"));
	   SomeClass obj = (SomeClass)binaryFormatter.Deserialize(memoryStream);
	   Console.WriteLine(obj.SomeProperty);
	   Console.WriteLine(obj.SomeOtherProperty);
	}
}

The above program merrily deserializes not only instances of SomeClass (even though a class cast error is raised for other objects), but also might be enough to trigger dangerous behaviors. For example, a malicious user could leverage publicly available tools such as ysoserial.net to easily craft payloads that exploit the presence of external libraries, and thus build a chain of gadgets that eventually lead to RCE.

Alternatively, an attacker with knowledge of the source code of the application could attempt to locate dangerous classes in the code base. For example, suppose that somewhere in the application, the following class is defined:

[Serializable]
public class DangerousClass
{
    private string path;

	public DangerousClass(String path) {
		this.path = path;
	}

	public ~DangerousClass() {
		File.Delete(path)
	}
}

The attacker is then able to build such objects locally using an arbitrary path as a parameter, serialize it, and finally feed it to the vulnerable application. When said object is eventually removed from memory by the garbage collector, the attacker gains the ability to delete arbitrary files in the system.

Prevention

Never pass user-supplied input to BinaryFormatter; the documentation states this explicitly:

The BinaryFormatter type is dangerous and is not recommended for data processing. Applications should stop using BinaryFormatter as soon as possible, even if they believe the data they’re processing to be trustworthy. BinaryFormatter is insecure and can’t be made secure.

When possible, developers are encouraged to use other forms of data serialization, such as XML, JSON, or the BinaryReader and BinaryWriter classes. The latter is the recommended approach for binary serialization. For example, in the above scenario, the serialization phase could be implemented as:

var someObject = new SomeClass();
someObject.SomeProperty = "some value";
someObject.SomeOtherProperty = 3.14;

using (BinaryWriter writer = new BinaryWriter(File.Open("untrusted.file", FileMode.Create)))
{
    writer.Write(someObject.SomeProperty);
    writer.Write(someObject.SomeOtherProperty);
}

And in turn, the deserialization phase as:

var someObject = new SomeClass();
using (BinaryReader reader = new BinaryReader(File.Open("untrusted.file", FileMode.Open)))
{
	someObject.SomeProperty = reader.ReadString();
	someObject.SomeOtherProperty = reader.ReadDouble();
}

References

OWASP - Deserialization Cheat Sheet Wikipedia - Serialization Microsoft - BinaryFormatter security guide Black Hat - Breaking .NET Through Serialization