我将我的类的一个实例序列化到一个文件中(使用 BinaryFormatter
)
之后,在另一个项目中,我想反序列化这个文件,但是没有成功,因为我的新项目没有我的旧类的描述。 .Deserialize()
发生异常
Unable to find assembly '*MyAssembly, Version=1.9.0.0, Culture=neutral, PublicKeyToken=null'.*".
但我有程序集的 .DLL,其中包含我要反序列化的旧类的描述。
我不想在项目中添加此 DLL 的引用(我希望能够反序列化任何类型的程序集的类...)
如何通知序列化器/反序列化器使用我动态加载的程序集?
最佳答案
假设您正在通过 Assembly.Load()
加载程序集或 Assembly.LoadFrom()
,然后如 this answer 中所述给 SerializationException for dynamically loaded Type 来自 Chris Shain , 您可以使用 AppDomain.AssemblyResolve
在反序列化期间加载动态程序集的事件。但是,出于安全原因,您将希望防止加载完全意外的程序集。
一种可能的实现方式是引入以下内容:
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;
};
}
}
}
然后,在启动的某个地方,添加一个合适的 ResolveEventHandler
至 AppDomain.CurrentDomain.AssemblyResolve
例如如下:
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;
}
这 ResolveEventHandler
检查请求的程序集是否具有您的动态程序集的名称,如果有,则从预期的完整路径加载当前版本。
另一种方法是编写自定义 SerializationBinder
并将其附加到 BinaryFormatter.Binder
.在 BindToType (string assemblyName, string typeName)
Binder 需要检查属于您的动态程序集的类型,并适本地绑定(bind)到它们。这里的技巧是处理动态加载的类型嵌套在另一个程序集中的泛型中的情况,例如一个List<MyClass>
.在那种情况下 assemblyName
将是 List<T>
的程序集名称不是 MyClass
.有关如何执行此操作的详细信息,请参阅
- 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
在comments @sgnsajgon问,我想知道为什么我不能像在项目中显式引用签名程序集时那样反序列化流 - 只是 formatter.Deserialize(stream)
没有别的。
虽然我不知道 Microsoft 员工在设计这些类时是怎么想的(回到 .Net 1.1 ),但可能是因为:
用 Eric Lippert 的话来说,no one ever designed, specified, implemented, tested, documented and shipped that feature.
BinaryFormatter
安全性已经有点 dumpster fire , 但自动调用Assembly.Load()
在BinaryFormatter
中任何意外的程序集名称流可能会使事情变得更糟。我所说的“垃圾箱火灾”是指开箱即用的
BinaryFormatter
将实例化并填充输入流中指定的类型这可能不是您期望的类型。因此你可能会这样做var instance = (MyClass)new BinaryFormatter().Deserialize(stream);
但是如果这个流实际上包含一个序列化的攻击工具,比如
TempFileCollection
然后小工具将被创建和填充,攻击将受到影响。(有关此类攻击的详细信息,请参阅 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 和 Alvaro Muñoz & Oleksandr Mirosh 的黑帽论文 https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf。这些链接指定了如何修改 Json.NET 的配置以启用此类攻击;
BinaryFormatter
在默认配置中易受攻击。)现在如果
BinaryFormatter
正在自动调用Assembly.Load()
在无法识别的程序集名称上,使用它的应用程序可能还会容易受到 DLL planting attack 的攻击 来自攻击 DLL 的攻击类型会从某个意外位置而不是安全位置意外加载,从而进一步加剧了攻击风险。(顺便说一句,如果您确实选择编写自己的
SerializationBinder
,您可以过滤掉意外类型和/或已知的攻击类型,从而降低攻击小工具注入(inject)的风险。自BinaryFormatter
以来,这也可能比预期的更难。流通常包含您可能不知道允许的序列化私有(private)或内部类。)
顺便说一句, What are the deficiencies of the built-in BinaryFormatter based .Net serialization? 对使用 BinaryFormatter
时可能遇到的其他问题进行了有用的概述。 .
关于c# - 使用动态加载的 .Net 程序集进行二进制序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18881659/