Insecure Binary Deserialization

The OWASP Top 10 was updated last year, and there are a couple of new items.  One of them is Insecure Binary Deserialization.  Many of us use serialization in our applications, weather we know it or not, and through it sounds obscure it is a significant vulnerability. I talked about the bug on the Application Security Podcast earlier this year.   In 2017, Paypal was hit by a serialization bug in their JBoss middleware because of an unpatched system.  Several .NET projects were found to use the insecure BinarySerializer class in 2016 as well.  It's a very real problem.

Let's take a quick tour of serialization, look at the ways it can be attacked, and what you should do about it, here as part of the C# Advent.

What the heck is serialization anyway?

 Serialization is just saving the state of an object to a storable format.  Encoding and character sets aside, it is usually the conversion of an object in a system to text, so that it can be written to disk in one way or another.  Consider the ASP.NET Session object.  To the developer, it is just a Collection of items, but once stored and sent, it becomes part of the ViewState hidden field.

Back in the day, if we canted to move an object over the wire we had to use a binary transfer protocol, like DCOM or CORBA.  They stunk - they were hard to use and suffered from vendor lock-in.  XML, and later JSON, were eventually made the standard for data serialization, but it still didn't give us a way to move good old objects around.  

Serialization of objects had been around for a while (that's what CORBA does under the covers) but it wasn't really part of the languages we were using back in the 90s, but frameworks like the JDK and .NET changed all of that.  Serialization became commonplace for transfer and storage of the state of an application, identity information, even just a generic object we wanted to persist for some reason.

In .NET, there are a number of serialization options.  After marking an object [Serializable] one can use a member of the System.Runtime.Serialization namespace to render the object to a stable format.

For instance, here is a bit of demo code that serialized a simple class with two properties, and saves it to disk.  Keep in mind, this is vulnerable code and should not be used in production!

SerializableClass sc = new SerializableClass();
sc.StringProperty = "Hello World!";
sc.IntegerProperty = 42;
BinaryFormatter fmt = new BinaryFormatter();
using (FileStream stm = File.OpenWrite(@"c:\temp\output.stm"))
    fmt.Serialize(stm, sc);

If we take a look at output.stm in a text editor, we will see that it is, well, binary, as one would imagine.

However, this is just an encoding issue.  If we load it up in UTF-8 we see that things get a lot more interesting.

How can something like that be insecure?

It's an injection vector, just like a querystring variable or a INPUT in a web form.  An attacker can modify those values and submit them to the server.

"Wait," you are probably thinking, "doesn't the framework make sure it hasn't changed?"

Well, no.  It doesn't, and therein lies the rub.  The issue, in the case of C# and the .NET Framework, with the BinaryFormatter.  When one deserializes the object, no checks at all take place - it is left in the hands of the developer.  Since most folks don't know there is a problem, most folks don't confirm that the object is as they expect.

If the developer is depending on data in that object, then we have a real consideration.  Bypassing some controls, an attacker might be able to discover SQL Injection, Directory Injection, or even Remote Code Execution commands on the operating system.  But that's just where it starts.  With no signature confirming that the object is as we left it, nothing is stopping an attacker from reverse engineering the object, writing their own application to deserialize it, and even change its functionality.

How does one fix this?

There are two main solutions to insecure deserialization.  The first is to know your framework.  BinarySerializer, the class I used in the example, is the only serialization class that remains vulnerable to this kinds of thing.  As such, if you have to use it, don't store anything that is important in the class being serialized.  Just like a cookie, right? You know the cookie is plain text on the user's computer, so you don't put anything sensitive in it.  Right?

And I know this is a C# article, but other languages have this problem as well.In Java, the ObjectInputScream is vulnerable and it is the most commonly used serializer in the language, so watch for that.  PHP doesn't have a secure serialization route at all - the unserialize method is the only way (so far as I know) to process a serialized object.  The Ruby Marshal.load() method is designed insecure, and they even warn you of it in the documentation.  This is not unlike the BinarySerializer - it lets you run with scissors so that you can solve certain hard-to-solve problems.

So what do you do if you have to use a vulnerable serializer?  Panic.  Not not really.  Start to think in terms of a positive security model.  If you are allowing something that has been outside the boundary of your system back into your system, you should only allow expected values and reject all others.  This starts with type checking, of course, but can extend further than that.  If you are expecting a ZipCode parameter to contain five digits, then anything other than that should set off alarm bells. Input validation is your friend here. 

Alright, what if I ignore this whole thing and hope it goes away?

 In C#land, the BinarySerializer can only be used to store properties, not methods.  Therefore, actual code injection is practically impossible.  If the data is such that you don't care if the user edits it (say, a local only game state file) then ignoring the problem might not cause you any really large scale problems.

However, if you are storing anything that is used in the running of the program, especially in network connected or web applications, then you have to be very careful of possible side effects.  Just like any other input, if you allow the user to change it outside the bounds of your user interface to are begging for problems.  Say you store the contents of the shopping cart in a serialized object.  Can the user change the quantity ordered to a -1?  Would that give them a discount?

At the very least testing of any serialized objects should be part of your vulnerability assessment plan.  Any time data crosses the boundary of your application, check it!

Comments (1) -

Comments are closed