Back in 2018, I wrote about Insecure Binary Deserialization, and I'd like to give an update here for C# Advent. Originally, OWASP had just added Insecure Binary Deserialization to the OWASP Top 10, and while the problem was primarily in Java and older serialization libraries, there were still some .NET libraries that were vulnerable. Well, no longer. Microsoft fixed the problem by deprecating the library in .NET 5.0, and here we will go over what we found in 2018, how Microsoft made the change, and what you need to do.
What is Serialization?
At its most straightforward, serialization is the act of taking an in-memory object and encoding it as a string. There is a bit more to it than that, but it doesn't change anything in this article, so we won't get super detailed here. Encoding an in-memory object as a string does have a myriad of uses, though, from saving game state to passing objects from server to server with REST services in JSON.
Let's use game save files as an example It is possible to save a serialized file as serialized binary instead of serialized text, but this is it very difficult to send over the internet, either via service or through a web page. If the game is standalone on PC and doesn't have to report state to a server then binary format is fine - it will appear as hex in Windows and honestly be just as editable. But, you got to do a lot more trial and error.
If the serialized object is text, then it is just a matter of decoding it, editing it, and saving it. It's pretty straightforward. Different frameworks use different methods, and sometimes different methods within the same framework use different methods.
Let's take a look at a quick example.
If I have a class like this:
public class SerializableClass
{
public string StringProperty { get; set; }
public int IntegerProperty { get; set; }
}
And I use BinaryFormatter.Serialize() to make a serialized object after setting some values, then save it as a file, I get this:
Pretty slick, eh?
How can Serialization be insecure?
This file is unsigned, has no Message Authentication Code, and is not encrypted.
That's the quick answer. The slow answer is that this file can be altered if you know what you are doing. I covered that pretty well in my 2018 article, so I'll let it pass here and refer you there. That said, this means that any "binary" object (which looks like a bunch of gobbligook to an inexperienced developer) it editable by an attacker. For instance, I changed a word (I'll let you guess which one) and changed the data in the program on file load:
This is just one example in C# - there are a number of examples of serializers that can become a security concern, for exactly the same reason. The thing is, there are a number of better classes that do the work properly. We just need to use them. So that leads to - what changed.
What changed?
TLDR: They started deprecating the classes that provide insecure serialization.
The Deets:
In .NET 5.0, Microsoft has begun deprecating the classes that provide insecure serialization. They started with the BinaryFormatter, and the pattern will gradually be used for all serialization methods. It's easy to say "Hey don't use these methods" but it is harder to follow up. So, as part of the Long Term Obsoletion Plan, use of the BinaryFormatter will cause a warning in your build. Eventually, over some time, that will become an error, and then in the fullness of time (h/t Don Box) the appropriate classes and methods will be removed. The goal, of course, is to simply have developers use more secure versions of the serialization classes.
So, for instance as seen at the early pre-release documentation at https://aka.ms/binaryformatter, the recommendation is to use JsonSerializer instead of BinaryFormatter. If you use BinaryFormatter, you will get warnings today, errors tomorrow, and it will eventually break your build. Over time the other insecure methods and classes will be given the same treatment.
In ASP.NET 5.0, BinaryFormatter is disabled by default. I recommend you do not override this, especially if you have a big project that uses serialization. Junior programmers and contractors will use BinaryFormatter because it is the tool they know, and it can sneak into your project. Blazor also disables BinaryFormatter by default. This is good news, because web applications are the weakest link much of the time, storing serialized objects in cookies, headers, and hidden form fields to simplify communication. An attacker can deserialize those blobs, then steal secrets or change values. You must be on the watch, and default disable is a great way to help with that.
BinaryFormatter was the first but will hardly be the last serialization feature to be deprecated. For instance, certain features of SecureString use serialization in an insecure way, and they are on their way out too.
What you need to do.
This is a fluid situation, so the best thing you can do is keep informed. Subscribe to the Binary Formatter Security Guide. As time passes, the updates will show there. You can also get involved! Two of the key folks involved in this are beer-drinking level friends of mine, and they assure me that the commitment to community involvement is as important if not more important to the security folks as it is to the rest of the developer community. Write some stuff!
Upgrade to .NET 5.0 as soon as you can. Then watch your warnings. If your code base is clean enough, break the build on warnings. Depending on your CICD situation, you might be able to only break on certain parts of code, which is awesome. That way, you can take advantage of the changes they are making to the framework as it happens.
Search your codebase for BinaryFormatter and replace instances with format specific instances. For instance, use BinaryReader and BinaryWriter for XML and JSON. Also, if you are working with data objects, take a look at DataContractSerializer. All of those classes validate inputs properly.
Avoid the SoapFormatter, LosFormatter, and ObjectStateFormatter completely. Those use the old underlying structure, and are not secure.
Finally, consider if you need to use a serialized object at all. There are a lot of better ways to solve that problem. For instance, make an indirect object reference, save the information server side and give the client the reference ID. That's better on all accounts.
Assume always that input is untrustworthy. Consider your risk model and then ask yourself "if someone sent in malicious input here, what could happen?" Dig deep. You might be surprised what you learn about your code.