Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binary serialization with dynamically loaded .Net assembly

I serialized an instance of my class into a file (with BinaryFormatter)

After, in another project, I wanted to deserialize this file, but it did not work because my new project does not have the description of my old class. The .Deserialize() gets an exception

Unable to find assembly '*MyAssembly, Version=1.9.0.0, Culture=neutral, PublicKeyToken=null'.*".

But I have the .DLL of the assembly containing a description of the old class which I want to deserialize.

I don't want to add a reference a this DLL in the project (I want be able to deserialize a class of any kind of assembly...)

How can I inform the Serializer/Deserializer to use my dynamically loaded assembly?

like image 828
Baud Avatar asked Sep 18 '13 20:09

Baud


People also ask

What is the difference between binary serialization and XML serialization?

Xml Serializer serializes only public member of object but Binary Serializer serializes all member whether public or private. In Xml Serialization, some of object state is only saved but in Binary Serialization, entire object state is saved.

Is binary serialization safe?

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.

What is binary serialization?

Binary serialization allows modifying private members inside an object and therefore changing the state of it. Because of this, other serialization frameworks, like System. Text. Json, that operate on the public API surface are recommended.

What is .NET serialization?

.Net Serialization (C#/VB.Net) Serialization can be defined as the process of converting the state of an object instance to a stream of data, so that it can be transported across the network or can be persisted in the storage location.


1 Answers

Assuming you are loading your assembly via Assembly.Load() or Assembly.LoadFrom(), then as explained in this answer to SerializationException for dynamically loaded Type by Chris Shain, you can use the AppDomain.AssemblyResolve event to load your dynamic assembly during deserialization. However, for security reasons you will want to prevent loading of entirely unexpected assemblies.

One possible implementation would be to introduce the following:

public class AssemblyResolver
{
    readonly string assemblyFullPath;
    readonly AssemblyName assemblyName;

    public AssemblyResolver(string assemblyName, string assemblyFullPath)
    {
        // You might want to validate here that assemblyPath really is an absolute not relative path.
        // See e.g. https://stackoverflow.com/questions/5565029/check-if-full-path-given
        this.assemblyFullPath = assemblyFullPath;
        this.assemblyName = new AssemblyName(assemblyName);
    }

    public ResolveEventHandler AssemblyResolve
    {
        get
        {
            return (o, a) =>
                {
                    var name = new AssemblyName(a.Name);
                    if (name.Name == assemblyName.Name) // Check only the name if you want to ignore version.  Otherwise you can just check string equality.
                        return Assembly.LoadFrom(assemblyFullPath);
                    return null;
                };
        }
    }
}

Then, somewhere in startup, add an appropriate ResolveEventHandler to AppDomain.CurrentDomain.AssemblyResolve e.g. as follows:

class Program
{
    const string assemblyFullPath = @"C:\Full-path-to-my-assembly\MyAssembly.dll";
    const string assemblyName = @"MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

    static Program()
    {
        AppDomain.CurrentDomain.AssemblyResolve += new AssemblyResolver(assemblyName, assemblyFullPath).AssemblyResolve;
    }

This ResolveEventHandler checks to see whether the requested assembly has your dynamic assembly's name, and if so, loads the current version from the expected full path.

An alternative would be to write a custom SerializationBinder and attach it to BinaryFormatter.Binder. In BindToType (string assemblyName, string typeName) the binder would need to check for types belonging to your dynamic assembly, and bind to them appropriately. The trick here is dealing with situations in which your dynamically loaded types are nested in a generic from another assembly, e.g. a List<MyClass>. In that case assemblyName will be the name of the assembly of List<T> not MyClass. For details on how to do this see

  • How to create a SerializationBinder for the Binary Formatter that handles the moving of types from one assembly and namespace to another.
  • BinaryFormatter deserialize gives SerializationException

In comments @sgnsajgon asked, I wonder why I cannot deserialize stream the same way I would do when signed assembly is explicitly referenced in project - just formatter.Deserialize(stream) and nothing else.

While I don't know what Microsoft employees were thinking when they designed these classes (back in .Net 1.1), it might be because:

  • In the words of Eric Lippert, no one ever designed, specified, implemented, tested, documented and shipped that feature.

  • BinaryFormatter security is already somewhat of a dumpster fire, but automatically calling Assembly.Load() on any unexpected assembly name in a BinaryFormatter stream might make things even worse.

    By "dumpster fire" I mean that, out-of-the-box, BinaryFormatter will instantiate and populate the types specified in the input stream which might not be the types you are expecting. Thus you might do

     var instance = (MyClass)new BinaryFormatter().Deserialize(stream);
    

    But if the stream actually contains a serialized attack gadget such as TempFileCollection then the gadget will be created and populated and the attack will be effected.

    (For details on this sort of attack, see TypeNameHandling caution in Newtonsoft Json, External json vulnerable because of Json.Net TypeNameHandling auto?, How to configure Json.NET to create a vulnerable web API and Alvaro Muñoz & Oleksandr Mirosh's blackhat paper https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf. Those links specify how to modify Json.NET's configuration to enable such attacks; BinaryFormatter is vulnerable to them in default configuration.)

    Now if BinaryFormatter were automatically calling Assembly.Load() on unrecognized assembly names, apps using it might additionally become vulnerable to a DLL planting attack where attack types from attack DLLs would get unexpectedly loaded from some unexpected location rather than a secure location, further exacerbating the attack risk.

    (Incidentally, if you do choose to write your own SerializationBinder you can filter out unexpected types and/or known attack types, thus reducing the risk of attack gadget injection. This also can be harder than expected since BinaryFormatter streams often include serialized private or internal classes that you may not know to allow.)

As an aside, What are the deficiencies of the built-in BinaryFormatter based .Net serialization? gives a useful overview of other problems you might encounter using BinaryFormatter.

like image 111
dbc Avatar answered Oct 26 '22 13:10

dbc