Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Announcement: BinaryFormatter is being removed in .NET 9 #98245

Open
blowdart opened this issue Feb 9, 2024 · 49 comments
Open

Announcement: BinaryFormatter is being removed in .NET 9 #98245

blowdart opened this issue Feb 9, 2024 · 49 comments
Labels
area-Meta binary-serialization breaking-change Issue or PR that represents a breaking API or functional change over a prerelease.
Milestone

Comments

@blowdart
Copy link
Contributor

blowdart commented Feb 9, 2024

Ever since .NET Core 1.0, we in .NET Security have been trying to lay BinaryFormatter to rest. It’s long been known by security practitioners that any deserializer, binary or text, which allows its input to carry information about the objects to be created is a security problem waiting to happen. There is even a Common Weakness Enumeration (CWE) that describes the issue, CWE-502 “Deserialization of Untrusted Data” and examples of this type of vulnerability run from security issues in Exchange through security issues in Apache. Within Microsoft the use of BinaryFormatter with untrusted input has caused many instances of heartache, late nights, and weekend work trying to produce a solution.

In .NET Core 1.0 we removed BinaryFormatter entirely due to its known risks, but without a clear path to using something safer customer demand brought it back to .NET Core 1.1. Since then, we have been on the path to removal, slowly turning it off by default in multiple project types but letting you opt-in via flags if you still needed it for backward compatibility. .NET 9 sees the culmination of this effort with the removal of BinaryFormatter. In .NET 9 these flags will no longer exist and the in-box implementation of BinaryFormatter will throw exceptions in any project type when you try to use it. However, if you are committed to using a class that cannot be made secure you will still be able to.

It's not that it’s binary

We pick on BinaryFormatter because of its ubiquitous use, but the problem is not that the payload is binary, the problem is that the payload tells .NET what objects to create. That capability is what makes BinaryFormatter so popular, you can throw pretty much any class at it, and it will serialize and deserialize it correctly without developers having to do any work. However, it is also what makes it so dangerous.

There are classes within .NET which, by design, can run arbitrary code, and if you try to deserialize those through any deserialization code that creates objects based on the payload you end up with “Remote Code Execution” (RCE), a class of security vulnerability that allows the attacker to run their own code within your application, under the privileges your application has.

This has been a known problem as far back as 2012, when the Black Hat security conference featured the canonical presentation on .NET’s version of the problem, Are you my type? Breaking .NET through Serialization, James Forshaw. There’s even a GitHub repository dedicated to hunting and showing these types of attacks in .NET, https://github.com/pwntester/ysoserial.net.

Imagine your code has a class DeleteAllMyData. If you accept a payload from untrusted input, for example a web page, an attacker can create a payload that contains an instance of DeleteAllMyData and when you deserialize that payload you will get a chance to discover if your database backups actually restore.

Other .NET serialization formats (and serialization formats in other languages and frameworks) allow for class information to be passed in a payload, to pick on our own framework SoapFormatter, LosFormatter, NetDataContractSerializer and ObjectStateFormatter all exhibit the same dangerous characteristics.

Even JSON.NET has an opt-in option to allow the creation of a payload that specifies class names, and that is a text format, not a binary one. Other JSON deserializers (both .NET and Java) suffered from the same problem, as demonstrated at Black Hat 2017, Friday the 13th JSON Attacks, Alvaro Muñoz & Oleksandr Mirosh.

Your takeaway should be that there’s nothing inherently wrong with a binary payload, it’s the fact that the BinaryFormatter payload contains instructions on the classes to be created, and what should go into that class’s properties that make it so dangerous. Other serializers, including some JSON serializers, also have the same ability, making them equally dangerous to use.

Real Word Examples of BinaryFormatter security bugs

Within Microsoft, BinaryFormatter has been the cause of a quite a few security problems, including:

  • In Exchange, CVE-2021-42321, where a binder had a spelling mistake that led to RCE.
  • In Azure DevOps, CVE-2019-1306, where invalid MarkDown used BinaryFormatter and granted RCE.
  • In SharePoint, CVE-2022-22005, where loading images for charts used BinaryFormatter and once again lead to RCE.

There has been an outright ban on using BinaryFormatter for new projects at Microsoft for years, and an ongoing effort to remove it from older code for almost as long as the usage ban in new code. We have added code analyzers, warnings, and public documentation, and yet still we see people using it and ending up with RCE bugs. It feels like we need to do more.

BinaryFormatter is not fixable

BinaryFormatter has other problems around algorithmic complexity, unbounded cache growth, and unintended object graph edge manipulation. If we take out one insecure piece, replace it with a secure piece, and keep going until all the insecure pieces are replaced there is not much of the original BinaryFormatter left. A secure version would have a vastly different API shape and it could not be a drop-in replacement. It would require massive refactoring of application code to use, so, really, would it even be BinaryFormatter anymore?

Serialization binders, a type of class used to inspect types during deserialization, are often proposed as a control mechanism to stop BinaryFormatter doing bad things, but there are code paths in BinaryFormatter that bypass binders by design, and writing a correct binder that isn’t vulnerable to a Denial of Service (DoS) attack is extremely hard.

The plan for .NET 9

For the last few .NET releases newly introduced project types such as Blazor or MAUI projects have been unable to use BinaryFormatter at all. ASP.NET projects are particularly vulnerable as their entire purpose is to take untrusted input and were also part of the ban. For some, older, project types you have been able to opt into using BinaryFormatter by setting a flag.

Our current intentions are:

  1. The in-box implementation of BinaryFormatter will throw exceptions when you try to use it, even if you set any of the settings that previously enabled its use. We are also removing these settings.
  2. A new NuGet package will be provided, (with a suitable name like Microsoft.Unsupported.BinaryFormatter) which you could add to your projects. With a small config change in your app, BinaryFormatter will appear again, which is useful if you judge the risk of BinaryFormatter acceptable for your use cases. This NuGet package will be permanently marked as vulnerable so that dependency and code scanners can detect BinaryFormatter usage easily.
  3. We will also provide a safe reader in its own NuGet package. This reader will allow you to read a BinaryFormatter payload and it will return an object graph describing the payload. Each node in the graph will represent an object in the payload and each edge represents a reference between objects.

The safe reader will allow you to evaluate the classes within a BinaryFormatter payload, providing a representation of the object graph with each node containing information on the object type, and a list of its fields and their values. The node structure we are considering looks as follows:

public class Node
{
    public SomeTypeDescriptor ObjectType;
    public Dictionary<string, object> Fields;
}

We will use a new class for the object type descriptor (whose name is yet to be decided) to impede naïve developers calling Type.GetType(theNodeType) and reintroducing the same fundamentally insecure behavior that we are trying to stop.

Say you had had a class Customer and a class Address, and your customer had a ShippingAddress and BillingAddress, both of which are properties in the customer class, with a type of Address. The fields within the Customer object node would be something like { "ShippingAddress", node_for_ship_addr } and { "BillingAddress", node_for_bill_addr }.

This graph will allow you to both validate the types in the payload against types you are expecting and provide data you can use to rehydrate your objects.

Preparing for migration

To migrate away from BinaryFormatter is going to take work. There is no drop-in replacement, nor is there a single path that fits everyone because everyone’s classes are different and there are too many varied use cases.

The first step to migrating away from BinaryFormatter should be selecting a new serialization format. Remember that binary representation is safe if the produced payload does not instruct the deserialization process to create types. The same restriction applies to text-based formats.

At Microsoft, a lot of teams have moved from BinaryFormatter to ProtoBuf (you will even see some familiar names in the commit history for the C# version). Other teams, whose data is text and where textual representation have advantages like easy searching have moved to JSON.

There are a lot of choices out there, but we cannot pick a winner for you. Each potential serializer has pluses and minuses, and how you weigh these is a decision only you can make for your application. Factors can include speed, memory consumption, storage costs, whether it is human readable, whether it is standards based and so on.

The ideal safe use of the proposed SafeReader class is to perform conversions of existing data into your chosen new serialization format and run this conversion in as much isolation as possible, for example a virtual machine or a container, again analyzing the types in each payload, rehydrating objects, and then reserializing them in your chosen serialization format.

If your class properties are primitive types, for example int, bool, or strings then the proposed safe reader could work as part of a replacement deserialization mechanism at runtime, leaving you to do the object rehydration once you have validated the types are those you expect.

Or, if you accept the risk of Remote Code Execution, or, if you are sure that no-one can tamper with your messages or data (such a promise is extremely hard to make for online systems or for desktop or mobile apps) then you might consider using the proposed NuGet package. If you choose to take this route, please remember that this package is unsupported. We will not fix security issues in BinaryFormatter.

Summary

If you are using BinaryFormatter now you should start looking at alternative serialization formats and decide what works for you in your specific circumstance, be it saved object graphs, network transports, temporary round tripped data, or any other type of data.
You should look at all your saved data and plan to convert any data serialized by BinaryFormatter into the new serialization format you have chosen.

You should evaluate how much backwards compatibility you need, and how long you need it for.

Finally, you need to get off BinaryFormatter, it is too dangerous to use any more and that is why it is leaving the .NET runtime.

@blowdart blowdart added area-Meta breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. labels Feb 9, 2024
@blowdart blowdart added this to the 9.0.0 milestone Feb 9, 2024
@ghost
Copy link

ghost commented Feb 9, 2024

Tagging subscribers to this area: @dotnet/area-meta
See info in area-owners.md if you want to be subscribed.

Issue Details

Ever since .NET Core 1.0, we in .NET Security have been trying to lay BinaryFormatter to rest. It’s long been known by security practitioners that any deserializer, binary or text, which allows its input to carry information about the objects to be created is a security problem waiting to happen. There is even a Common Weakness Enumeration (CWE) that describes the issue, CWE-502 “Deserialization of Untrusted Data” and examples of this type of vulnerability run from security issues in Exchange through security issues in Apache. Within Microsoft the use of BinaryFormatter with untrusted input has caused many instances of heartache, late nights, and weekend work trying to produce a solution.

In .NET Core 1.0 we removed BinaryFormatter entirely due to its known risks, but without a clear path to using something safer customer demand brought it back to .NET Core 1.1. Since then, we have been on the path to removal, slowly turning it off by default in multiple project types but letting you opt-in via flags if you still needed it for backward compatibility. .NET 9 sees the culmination of this effort with the removal of BinaryFormatter. In .NET 9 these flags will no longer exist and the in-box implementation of BinaryFormatter will throw exceptions in any project type when you try to use it. However, if you are committed to using a class that cannot be made secure you will still be able to.

It's not that it’s binary

We pick on BinaryFormatter because of its ubiquitous use, but the problem is not that the payload is binary, the problem is that the payload tells .NET what objects to create. That capability is what makes BinaryFormatter so popular, you can throw pretty much any class at it, and it will serialize and deserialize it correctly without developers having to do any work. However, it is also what makes it so dangerous.

There are classes within .NET which, by design, can run arbitrary code, and if you try to deserialize those through any deserialization code that creates objects based on the payload you end up with “Remote Code Execution” (RCE), a class of security vulnerability that allows the attacker to run their own code within your application, under the privileges your application has.

This has been a known problem as far back as 2012, when the Black Hat security conference featured the canonical presentation on .NET’s version of the problem, Are you my type? Breaking .NET through Serialization, James Forshaw. There’s even a GitHub repository dedicated to hunting and showing these types of attacks in .NET, https://github.com/pwntester/ysoserial.net.

Imagine your code has a class DeleteAllMyData. If you accept a payload from untrusted input, for example a web page, an attacker can create a payload that contains an instance of DeleteAllMyData and when you deserialize that payload you will get a chance to discover if your database backups actually restore.

Other .NET serialization formats (and serialization formats in other languages and frameworks) allow for class information to be passed in a payload, to pick on our own framework SoapFormatter, LosFormatter, NetDataContractSerializer and ObjectStateFormatter all exhibit the same dangerous characteristics.

Even JSON.NET has an opt-in option to allow the creation of a payload that specifies class names, and that is a text format, not a binary one. Other JSON deserializers (both .NET and Java) suffered from the same problem, as demonstrated at Black Hat 2017, Friday the 13th JSON Attacks, Alvaro Muñoz & Oleksandr Mirosh.

Your takeaway should be that there’s nothing inherently wrong with a binary payload, it’s the fact that the BinaryFormatter payload contains instructions on the classes to be created, and what should go into that class’s properties that make it so dangerous. Other serializers, including some JSON serializers, also have the same ability, making them equally dangerous to use.

Real Word Examples of BinaryFormatter security bugs

Within Microsoft, BinaryFormatter has been the cause of a quite a few security problems, including:

  • In Exchange, CVE-2021-42321, where a binder had a spelling mistake that led to RCE.
  • In Azure DevOps, CVE-2019-1306, where invalid MarkDown used BinaryFormatter and granted RCE.
  • In SharePoint, CVE-2022-22005, where loading images for charts used BinaryFormatter and once again lead to RCE.

There has been an outright ban on using BinaryFormatter for new projects at Microsoft for years, and an ongoing effort to remove it from older code for almost as long as the usage ban in new code. We have added code analyzers, warnings, and public documentation, and yet still we see people using it and ending up with RCE bugs. It feels like we need to do more.

BinaryFormatter is not fixable

BinaryFormatter has other problems around algorithmic complexity, unbounded cache growth, and unintended object graph edge manipulation. Fixing it turns into a Ship of Theseus exercise. If we take out one insecure piece, replace it with a secure piece, and keep going until all the insecure pieces are replaced there is not much of the original BinaryFormatter left. A secure version would have a vastly different API shape and it could not be a drop-in replacement. It would require massive refactoring of application code to use, so, really, would it even be BinaryFormatter anymore?

Serialization binders, a type of class used to inspect types during deserialization, are often proposed as a control mechanism to stop BinaryFormatter doing bad things, but there are code paths in BinaryFormatter that bypass binders by design, and writing a correct binder that isn’t vulnerable to a Denial of Service (DoS) attack is extremely hard.

The plan for .NET 9

For the last few .NET releases newly introduced project types such as Blazor or MAUI projects have been unable to use BinaryFormatter at all. ASP.NET projects are particularly vulnerable as their entire purpose is to take untrusted input and were also part of the ban. For some, older, project types you have been able to opt into using BinaryFormatter by setting a flag.

Our current intentions are:

  1. The in-box implementation of BinaryFormatter will throw exceptions when you try to use it, even if you set any of the settings that previously enabled its use. We are also removing these settings.
  2. A new NuGet package will be provided, (with a suitable name like Microsoft.Unsupported.BinaryFormatter) which you could add to your projects. With a small config change in your app, BinaryFormatter will appear again, which is useful if you judge the risk of BinaryFormatter acceptable for your use cases. This NuGet package will be permanently marked as vulnerable so that dependency and code scanners can detect BinaryFormatter usage easily.
  3. We will also provide a safe reader in its own NuGet package. This reader will allow you to read a BinaryFormatter payload and it will return an object graph describing the payload. Each node in the graph will represent an object in the payload and each edge represents a reference between objects.

The safe reader will allow you to evaluate the classes within a BinaryFormatter payload, providing a representation of the object graph with each node containing information on the object type, and a list of its fields and their values. The node structure we are considering looks as follows:

public class Node
{
    public SomeTypeDescriptor ObjectType;
    public Dictionary<string, object> Fields;
}

We will use a new class for the object type descriptor (whose name is yet to be decided) to impede naïve developers calling Type.GetType(theNodeType) and reintroducing the same fundamentally insecure behavior that we are trying to stop.

Say you had had a class Customer and a class Address, and your customer had a ShippingAddress and BillingAddress, both of which are properties in the customer class, with a type of Address. The fields within the Customer object node would be something like { "ShippingAddress", node_for_ship_addr } and { "BillingAddress", node_for_bill_addr }.

This graph will allow you to both validate the types in the payload against types you are expecting and provide data you can use to rehydrate your objects.

Preparing for migration

To migrate away from BinaryFormatter is going to take work. There is no drop-in replacement, nor is there a single path that fits everyone because everyone’s classes are different and there are too many varied use cases.

The first step to migrating away from BinaryFormatter should be selecting a new serialization format. Remember that binary representation is safe if the produced payload does not instruct the deserialization process to create types. The same restriction applies to text-based formats.

At Microsoft, a lot of teams have moved from BinaryFormatter to [ProtoBuf](https://protobuf.dev/getting-started/csharptutorial/) (you will even see some familiar names in the commit history for the C# version). Other teams, whose data is text and where textual representation have advantages like easy searching have moved to JSON.

There are a lot of choices out there, but we cannot pick a winner for you. Each potential serializer has pluses and minuses, and how you weigh these is a decision only you can make for your application. Factors can include speed, memory consumption, storage costs, whether it is human readable, whether it is standards based and so on.

The ideal safe use of the proposed SafeReader class is to perform conversions of existing data into your chosen new serialization format and run this conversion in as much isolation as possible, for example a virtual machine or a container, again analyzing the types in each payload, rehydrating objects, and then reserializing them in your chosen serialization format.

If your class properties are primitive types, for example int, bool, or strings then the proposed safe reader could work as part of a replacement deserialization mechanism at runtime, leaving you to do the object rehydration once you have validated the types are those you expect.

Or, if you accept the risk of Remote Code Execution, or, if you are sure that no-one can tamper with your messages or data (such a promise is extremely hard to make for online systems or for desktop or mobile apps) then you might consider using the proposed NuGet package. If you choose to take this route, please remember that this package is unsupported. We will not fix security issues in BinaryFormatter.

Summary

If you are using BinaryFormatter now you should start looking at alternative serialization formats and decide what works for you in your specific circumstance, be it saved object graphs, network transports, temporary round tripped data, or any other type of data.
You should look at all your saved data and plan to convert any data serialized by BinaryFormatter into the new serialization format you have chosen.

You should evaluate how much backwards compatibility you need, and how long you need it for.

Finally, you need to get off BinaryFormatter, it is too dangerous to use any more and that is why it is leaving the .NET runtime.

Author: blowdart
Assignees: -
Labels:

area-Meta, breaking-change

Milestone: 9.0.0

@ghost ghost added the needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet label Feb 9, 2024
@ghost
Copy link

ghost commented Feb 9, 2024

Added needs-breaking-change-doc-created label because this issue has the breaking-change label.

  1. Create and link to this issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.

Tagging @dotnet/compat for awareness of the breaking change.

@Nacimota
Copy link

I saw this in my RSS feed for the .NET blog, but it seems like the post itself has been taken down? I'm curious what's going on there.

In any case, I do remember being warned against using BinaryFormatter a few years back but never really understood or looked into what the problem was. I feel like this announcement is an excellent summary, though; very informative, thank you. I also think the plan going forward for .NET 9 sounds quite sensible. 👍

@ltrzesniewski
Copy link
Contributor

ltrzesniewski commented Feb 10, 2024

Have you considered adding a migration path for [Serializable] framework classes such as CookieContainer?

I'm thinking about this class in particular since I'm using BinaryFormatter to serialize cookies as there's no better choice AFAIK. It makes sense to have a serialization mechanism for cookies since their whole point is storing data. In my case, I serialize them to preserve them through a process restart.

I suppose the new NuGet package could be a problem for framework classes in the long run, since comments such as // Do not rename (binary serialization) on existing fields would no longer need to be enforced at some future point in time, and existing serialized data could become incompatible after a runtime update.

@blowdart
Copy link
Contributor Author

@Nacimota The blog thing was totally my fault. I missed the new internal guidance to put things like this on github first :)

@blowdart
Copy link
Contributor Author

@ltrzesniewski As I said above, we can't choose an alternative for you, even for things marked [Serializable].

For cookies, the use of any serializer that allows type specification in the payload is a bad idea, unless you're also signing that payload with an HMAC, and then managing the keys. If you're not, your inbound cookies are a vector for remote code execution.

It's interesting you mention updates, I don't think we never promised compatibility in serialized payloads between major version updates, we just tried hard to make sure it didn't break.

@ltrzesniewski
Copy link
Contributor

IIRC the last time I checked, the CookieContainer lacked a feature which would let you retrieve all of its internal state for serialization, and BinaryFormatter was the only way to round-trip all of its contents properly (and that made me sad 😅).

But that was a while ago, and I don't remember which part of the data I was unable to retrieve back then, so I guess I'll give it another try, and I'll open a separate issue if there's still anything lacking.

In any case, I was mentioning this example as there may be other classes similar to CookieContainer that don't expose all of their internal data easily, and that would be a problem for porting them from BinaryFormatter. My point is that a review of [Serializable] classes may be needed, if you haven't done so yet.

@AustinWise
Copy link
Contributor

AustinWise commented Feb 10, 2024

If I'm reading the design doc from 2020 correct, SerializationInfo and existing constructors on exceptions that take that parameter will remain functional in the product indefinitely? Is that correct?

(I am aware that if they are removed I can switch to using ExceptionDispatchInfo.SetRemoteStackTrace for most of my use case of transmitting exceptions over the network.)

@vargaz vargaz unpinned this issue Feb 10, 2024
@blowdart blowdart pinned this issue Feb 11, 2024
@Duranom
Copy link

Duranom commented Feb 11, 2024

Was rather happy to see the title and that it would finally be removed so companies would stop postponing taking action.
However sadly a few statements in there like a nuget package will be made available make me sigh again. I can understand giving options and all, but sometimes it is just making things worse and this is one of them.

Companies will not think for a second to just go with the new package and put up a rule for scanners that will accept any issue raised by the package, under the thought "we will migrate in future" but which gets delayed for potentially forever till .NET Framework is officially unsupported. (I know a few who will be like that)

Not to mention companies who are not yet over to .NET and their manager will use this as an additional excuse to moan about to not to migrate from .NET Framework to .NET because it blocks them.
(Even though they they can and should have ages ago, but they are either just waiting for .NET team to succumb to provide something to do all the work. And seeing the amount people/support from MS to help such companies, it seems that might be a reality I sometime I worry)

Really hope it gets fully removed, or at least in next LTS version. As .NET really needs scrubbing obsolete code instead of letting it roam around.

@TeddyAlbina
Copy link

For older version of dotnet is there a way to disable it?

@reflectronic
Copy link
Contributor

For older version of dotnet is there a way to disable it?

Since .NET 5, it is possible to use <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization> to completely disable BinaryFormatter at runtime. This is currently the default behavior for certain project types.

https://learn.microsoft.com/en-us/dotnet/core/compatibility/serialization/7.0/binaryformatter-apis-produce-errors#enableunsafebinaryformatterserialization-property

@blowdart
Copy link
Contributor Author

@Duranom unfortunately we're stuck in a tricky situation with this. When we released .NET Core 1.0, with BinaryFormatter removed entirely someone took the code from .NET Framework, built it into a nuget package and released it. Additionally, there were a lot of very emails from large customers to management stating people couldn't migrate from framework because it was missing.

We did debate the "burn it all down" approach, but pragmatism steered us in this direction.

With the new package we have something under our own control, which we will flag as code containing a security vulnerability. This will light up errors during builds you'll have to suppress. It will also make it easy for security scanners to detect the continuing use of BinaryFormatter, and, if you're not using a vulnerability scanner (because they can be expensive) a simply search over your hard drive will allow you to look for the new package.

The package will also allow folks to move forward to .NET 9, or 10, or 11 etc., allowing them to keep going when a .NET version falls into end of life and no longer gets security patches.

@markwaterman
Copy link

What'll be the story with .NET Standard? Should .NET 9 not support netstandard2.0 and netstandard2.1 since they specify BinaryFormatter support? Seems like removal of an API is breaking change that would justify going to netstandard3.0 (assuming netstandard follows semantic versioning rules).

I have a library that I ship as a netstandard2 NuGet package, so this puts me in a weird spot because now I have to pull the BinaryFormatter out of the package, which will break some customers' existing .NET 6/7 apps. While it's reasonable for me to tell a customer that it's time to move to protobuf if they want .NET 9, it's not going to be pleasant to force existing .NET 6 customers to migrate to protobuf because of a change in another runtime that they aren't targeting. So it would be nice if I could add a new, BinaryFormatterless netstandard3 target to my NuGet package that only .NET 9+ apps would be able to pull in.

@blowdart
Copy link
Contributor Author

blowdart commented Feb 13, 2024

@markwaterman These days you target the TFM unless you're sharing. I don't think you're going to see a .NET Standard 3.0, but those folks could surprise me. However when we say we're removing it we are removing the functionality, not the API. The BinaryFormatter class will still be there, it will just throw when you try to use it. The .NET Standards are for API surface, not functionality, so technically we're fine.

If you're using BinaryFormatter in your nuget package you're already in an odd place, because it can already be disabled and uncallable in some project types.

As I'm the security tpm my advice would be to stop using it on all versions, including netstandard2.0 and 2.1 because it's too dangerous to keep using. But, if you don't want to force your customers you'd pull in the new unsupported package, and keep going, with all the security warnings that will entail. You face the same problem we have, and we've chosen to force the issue.

@osexpert
Copy link

"Unsupported.BinaryFormatter) which you could add to your projects. With a small config change in your app, BinaryFormatter will appear again"
What config would it be and why is a config needed at all?

@blowdart
Copy link
Contributor Author

@osexpert Config will tell the runtime to swap the inbox "throw on every call" version of BinaryFormatter with the unsupported, insecure package which provides the old serialization and deserialization behaviour.

The format of the config entry is to be decided.

@osexpert
Copy link

osexpert commented Feb 13, 2024

@osexpert Config will tell the runtime to swap the inbox "throw on every call" version of BinaryFormatter with the unsupported, insecure package which provides the old serialization and deserialization behaviour.

The format of the config entry is to be decided.

So the runtime BinaryFormatter will somehow forward to a BinaryFormatter in Microsoft.Unsupported.BinaryFormatter? So the runtime will need to know about\rely on\depens on Microsoft.Unsupported.BinaryFormatter somehow?

Could the BinaryFormatter in Microsoft.Unsupported.BinaryFormatter be in its own namespace, without any forwarding from the runtime BinaryFormatter? It would not be drop-in compatible (would need to change the namespace and recompile), but it feels cleaner and would allow the runtime to not know\care about Microsoft.Unsupported.BinaryFormatter and vice versa?

@blowdart
Copy link
Contributor Author

The runtime will know about the config hooks but you don't have to set them. The unsupported version will probably (we're discussing it and there's a huge white paper being written) be in its own namespace where you don't have to hook it up, so the in box version will still throw. The config is there as a dangerous re-enablement feature.

@mdell-seradex
Copy link

mdell-seradex commented Mar 6, 2024

No I wasn't referring to things like BinaryFormatter.
I was referring to deserializing in general. Specifically, when deserializing an object where the payload doesn't specify the type.
You could still deserialize a TempFileCollection that has web.config listed in it, but only if your assembly expected to deserialize a TempFileCollection object. My expectation in that case is that you could add validation to ensure that you are not deserializing a malicious version of an object that you expect. Perhaps you would use text serialization, which may allow analyzing the object better, or perhaps you would deserialize it essentially in a sandbox to allow inspection of the object.
I haven't reviewed how an object is serialized with text serialization. I suspect that some data might be encoded using Base64 encoding, but most would probably be plain readable text.

Added after IS4Code's update below:
Of course if you also have complete and utter control over the source, then this really shouldn't happen. It would only happen if the source was compromised, or you accepted input from external sources.

@IS4Code
Copy link

IS4Code commented Mar 6, 2024

The deserialization of any object, where the payload specifies which object class is to be created is the danger. It doesn't even take nesting. It's that the payload controls what class is used.

It is dangerous only if the source of the payload has the potential to be compromised. If you have full control over the data that gets seralized, then it is completely safe, because at that point you are basically hacking yourself with harmful input. The danger lies purely in the fact that BinaryFormatter allows the payload to activate an arbitrary class and set arbitrary properties.

The difference between BinaryFormatter and JSON.NET or MessagePack, where such a thing is also perfectly possible, is that BinaryFormatter has it enabled "by default", and the whole concept really does not work without it (of course you can still explore the object graph passively, which is essentially equivalent to things like loading an assembly in reflection-only mode).

It is incorrect to treat BinaryFormatter as "always dangerous" ‒ there are a few use-cases where there is no problem with using it:

  • Does the data come from inside of your application, i.e. the same place where it is also deserialized? ‒ If so, safe. Using BinaryFormatter to duplicate an object graph is perfectly legitimate.
  • Is the data stored alongside your application as a static configuration, with the same permissions as the executable? ‒ If so, safe. Using it for external immutable configuration is fine, because if someone was to attack your application using that, they could just modify the application executable.
  • Does your application run in a sandbox or an embedded environment, where the data comes directly from the host environment (or an environment above it)? ‒ If so, safe. If the host is compromised, worrying about getting a dangerous payload into a sandbox is the least of your concerns. If the code runs in a Blazor WebAssembly client-side app, then congratulations, the user has just hacked their own browser.

Of course there are other situations where it is decidedly unsafe:

  • The data comes from a socket from another process/machine/node etc. If that is compromised, it may easily spread to other instances through this.
  • The data comes from a user-provided file, i.e. from a less privileged environment to a more privileged environment.

@osexpert
Copy link

osexpert commented Mar 6, 2024

but BinaryFormatter by design bypasses binders in a bunch of circumstances.

At least I can not see this bypass easily in the code. AFAICS, before all uses of Assembly.Load and Type.GetType, it tries the binder first and continue if binder return null. But I may be wrong\missing something. So the binder would have to throw on blocked types, never return null, to be "safe". Maintaining the list of allowed types will be a PITA, but for some huge legacy systems it may be last resort.

@blowdart
Copy link
Contributor Author

blowdart commented Mar 7, 2024

it is incorrect to treat BinaryFormatter as "always dangerous"

That's fair, it can be used safely, webforms does it with viewstate for example. But our experience of its use shows that most uses are unsafe. It's hard to make data trusted.

If you believe your data is fine, then that's what the unsupported package will be for, just wire it in again and away you go.

@blowdart
Copy link
Contributor Author

blowdart commented Mar 7, 2024

My expectation in that case is that you could add validation to ensure that you are not deserializing a malicious version of an object that you expect. Perhaps you would use text serialization, which may allow analyzing the object better, or perhaps you would deserialize it essentially in a sandbox to allow inspection of the object.

That's what we expect the safe parser to offer, an object graph you can walk to make sure the classes are what you expect, but a constrained serializer where you have to specify the object you want to deserialize into is going to be simpler and safer to use.

@MauNguyenVan
Copy link

More security more better. Well come.

@beyond-code-github
Copy link

A new NuGet package will be provided, (with a suitable name like Microsoft.Unsupported.BinaryFormatter)

Great write-up. Does anyone know if this has been created?

@julealgon
Copy link

@blowdart

...it can be used safely, webforms does it with viewstate for example.

Since WebForms is still officially supported, I take it this means the BinaryFormatter implementation in .NET Framework will still be supported for the foreseeable future? Is there any alternative for folks still using WebForms to move away from BinaryFormatter while keeping the same underlying functionality (e.g. viewstate)?

And another question. You said it would be impossible to change BinaryFormatter in a way that would make it safe, without completely rewriting the entire thing including its API at which point it wouldn't even be recognizable anymore.

However, wouldn't something like a required list of known types that the developer needs to provide substantially improve security of this class? Wouldn't that be somewhat equivalent to what happens with XML and Json inheritance serialization today, where you provide an explicit list of known types in the base class so that the serializer can create those different objects based on some sort of discriminator in the payload?

If BinaryFormatter's API required an explicit set of types to be provided, it could validate the types being asked in the payload against that list and throw exceptions when unlisted types are used. At that point, the unsafety of the library would only surface if the user explicitly provided a type that had sensitive operations or behaviors as part of that list of types, correct?

@blowdart
Copy link
Contributor Author

blowdart commented Jun 7, 2024

@julealgon WinForms is on a path to move away from BinaryFormatter use in .NET. .NET framework will remain as is, it is not affected by any of this.

Requiring a list of known types, well, you're making my point for me I'm afraid. Once the deserialization API changes it's just not BinaryFormatter. The whole point of BinaryFormatter is unconstrained serialization.

@blowdart
Copy link
Contributor Author

blowdart commented Jun 7, 2024

@beyond-code-github Not yet. It underwent API review this week.

@julealgon
Copy link

@blowdart

@julealgon WebForms is on a path to move away from BinaryFormatter use in .NET. .NET framework will remain as is, it is not affected by any of this.

I take it you meant to say Windows Forms there? I'm talking about the ASP.NET WebForms framework which is only available in .NET Framework.

Requiring a list of known types, well, you're making my point for me I'm afraid. Once the deserialization API changes it's just not BinaryFormatter. The whole point of BinaryFormatter is unconstrained serialization.

My point was that this wouldn't be that massive of a change in terms of API contract, and could fit the bill for many simpler scenarios with a very small code change. Wouldn't that be worth pursuing?

@blowdart
Copy link
Contributor Author

blowdart commented Jun 7, 2024

Oof you're right, editing. Not enough coffee yet :)

But any API change like that is a massive change. Anyone using BinaryFormatter would be immediately broken. It's exactly what we want to avoid.

@julealgon
Copy link

Oof you're right, editing. Not enough coffee yet :)

I'm sorry @blowdart , I see your update but I already knew the situation regarding WinForms. Can you clarify on the rest of my post in terms of WebForms though?

@blowdart
Copy link
Contributor Author

blowdart commented Jun 7, 2024

WebForms is a .NET Framework thing. Nothing here affects Framework in anyway.

@IrinaPykhova
Copy link

IrinaPykhova commented Jun 10, 2024

What is the current state of removing binary formatter? I'm testing .Net 9 Preview 4 with my .Net 6 controls which have serializable objects and use them for drag&drop. Apparently, DataObject class still uses binary serialization and it only fails on attempt to deserialize with exception about not supported binary formatter. What are your plans about this, should I try to fix it from my side and serialize object manually before creating DataObject or you will try to use other formatter from your side? Is it time to post you bugs about this? It affects both WPF and WinForms, only exception text I get is a bit different.
I haven't checked yet, but suppose I'll have the same question about clipboard. We want our controls to be compatible with .Net 9 by November, so I need to know more details in advance.

@merriemcgaw
Copy link
Member

@IrinaPykhova - @JeremyKuhne, @adamsitnik, @lonitra are all working hard on getting all the pieces lined up for the final removal of BinaryFormatter, and @KlausLoeffelmann is working on Analyzers to help people understand when they unexpectedly (in a WinForms app) are doing something that may eventually use BF. We should be ready for a broad update very soon, the plan approved, and we are all executing. Feel free to ping us in the designer repo for details, specifically on how it may impact control developers in the short term. I'll circle back here as soon as we have firm timelines so we can share the plan and timelines broadly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Meta binary-serialization breaking-change Issue or PR that represents a breaking API or functional change over a prerelease.
Projects
None yet
Development

No branches or pull requests