- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在寻找一种简单而安全的方法来从 .NET 应用程序访问插件。尽管我认为这是一个非常常见的要求,但我正在努力寻找满足我所有需求的任何东西:
我已经研究了 MEF 和 MAF,但我正在努力了解如何使它们中的任何一个能够满足要求。
假设我的理解是正确的,MAF 无法支持跨其隔离边界传递泛型类型,这对我的应用程序至关重要。 (MAF 的实现也非常复杂,但如果我能解决泛型类型问题,我就准备好使用它)。
MEF 几乎是一个完美的解决方案,但似乎达不到安全要求,因为它在与主机相同的 AppDomain 中加载其扩展程序集,因此显然可以防止沙箱。
我见过this question ,其中谈到在沙盒模式下运行 MEF,但没有描述如何运行。 This post声明“使用 MEF 时,您必须信任扩展不会运行恶意代码,或通过代码访问安全性提供保护”,但它同样没有描述如何操作。最后还有this post ,它描述了如何防止未知插件被加载,但这不适合我的情况,因为即使是合法的插件也会是未知的。
我已成功将 .NET 4.0 安全属性应用到我的程序集,并且 MEF 正确地遵守了它们,但我不明白这如何帮助我锁定恶意代码,因为许多框架方法可能是安全威胁(例如 System.IO.File
的方法)被标记为 SecuritySafeCritical
,这意味着它们可以从 SecurityTransparent
程序集访问。我在这里错过了什么吗?我可以采取一些额外的步骤来告诉 MEF 它应该为插件程序集提供互联网权限吗?
最后,我还考虑使用单独的 AppDomain 创建自己的简单沙盒插件架构,如 here 所述。 。然而,据我所知,这种技术只允许我使用后期绑定(bind)来调用不受信任的程序集中的类的静态方法。当我尝试扩展此方法来创建我的插件类之一的实例时,返回的实例无法转换为公共(public)插件接口(interface),这意味着主机应用程序无法调用它。我可以使用某种技术来跨 AppDomain 边界进行强类型代理访问吗?
对于这个问题的长度,我深表歉意;原因是展示我已经研究过的所有途径,希望有人可以建议一些新的尝试。
非常感谢您的想法,蒂姆
最佳答案
我已经接受了 Alastair Maw 的回答,因为正是他的建议和链接让我找到了一个可行的解决方案,但我在这里发布了我所做的一些细节,供其他可能试图实现类似目标的人引用。
提醒一下,我的应用程序最简单的形式包含三个程序集:
下面的代码是我的真实代码的简化版本,仅显示发现和加载插件所需的内容,每个插件都有自己的AppDomain
:
从主应用程序程序集开始,主程序类使用名为 PluginFinder
的实用程序类来发现指定插件文件夹中任何程序集中的合格插件类型。然后,对于每种类型,它都会创建一个 sandox AppDomain
实例(具有 Internet 区域权限),并使用它来创建发现的插件类型的实例。
创建具有有限权限的AppDomain
时,可以指定一个或多个不受这些权限约束的受信任程序集。为了在此处介绍的场景中实现此目的,必须对主应用程序程序集及其依赖项(互操作程序集)进行签名。
对于每个加载的插件实例,可以通过其已知接口(interface)调用插件内的自定义方法,并且插件还可以通过其已知接口(interface)回调到主机应用程序。最后,主机应用程序卸载每个沙箱域。
class Program
{
static void Main()
{
var domains = new List<AppDomain>();
var plugins = new List<PluginBase>();
var types = PluginFinder.FindPlugins();
var host = new Host();
foreach (var type in types)
{
var domain = CreateSandboxDomain("Sandbox Domain", PluginFinder.PluginPath, SecurityZone.Internet);
plugins.Add((PluginBase)domain.CreateInstanceAndUnwrap(type.AssemblyName, type.TypeName));
domains.Add(domain);
}
foreach (var plugin in plugins)
{
plugin.Initialize(host);
plugin.SaySomething();
plugin.CallBackToHost();
// To prove that the sandbox security is working we can call a plugin method that does something
// dangerous, which throws an exception because the plugin assembly has insufficient permissions.
//plugin.DoSomethingDangerous();
}
foreach (var domain in domains)
{
AppDomain.Unload(domain);
}
Console.ReadLine();
}
/// <summary>
/// Returns a new <see cref="AppDomain"/> according to the specified criteria.
/// </summary>
/// <param name="name">The name to be assigned to the new instance.</param>
/// <param name="path">The root folder path in which assemblies will be resolved.</param>
/// <param name="zone">A <see cref="SecurityZone"/> that determines the permission set to be assigned to this instance.</param>
/// <returns></returns>
public static AppDomain CreateSandboxDomain(
string name,
string path,
SecurityZone zone)
{
var setup = new AppDomainSetup { ApplicationBase = Path.GetFullPath(path) };
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(zone));
var permissions = SecurityManager.GetStandardSandbox(evidence);
var strongName = typeof(Program).Assembly.Evidence.GetHostEvidence<StrongName>();
return AppDomain.CreateDomain(name, null, setup, permissions, strongName);
}
}
在此示例代码中,主机应用程序类非常简单,仅公开一种可由插件调用的方法。但是,此类必须从 MarshalByRefObject
派生,以便可以在应用程序域之间引用它。
/// <summary>
/// The host class that exposes functionality that plugins may call.
/// </summary>
public class Host : MarshalByRefObject, IHost
{
public void SaySomething()
{
Console.WriteLine("This is the host executing a method invoked by a plugin");
}
}
PluginFinder
类只有一个公共(public)方法,该方法返回已发现的插件类型的列表。此发现过程加载它找到的每个程序集,并使用反射来识别其合格类型。由于此过程可能会加载许多程序集(其中一些甚至不包含插件类型),因此它也在单独的应用程序域中执行,该应用程序域可能随后被卸载。请注意,由于上述原因,此类还继承了 MarshalByRefObject
。由于 Type
的实例可能无法在应用程序域之间传递,因此此发现过程使用名为 TypeLocator
的自定义类型来存储每个已发现类型的字符串名称和程序集名称,这可能会导致然后安全地传回主应用程序域。
/// <summary>
/// Safely identifies assemblies within a designated plugin directory that contain qualifying plugin types.
/// </summary>
internal class PluginFinder : MarshalByRefObject
{
internal const string PluginPath = @"..\..\..\Plugins\Output";
private readonly Type _pluginBaseType;
/// <summary>
/// Initializes a new instance of the <see cref="PluginFinder"/> class.
/// </summary>
public PluginFinder()
{
// For some reason, compile-time types are not reference equal to the corresponding types referenced
// in each plugin assembly, so equality must be tested by loading types by name from the Interop assembly.
var interopAssemblyFile = Path.GetFullPath(Path.Combine(PluginPath, typeof(PluginBase).Assembly.GetName().Name) + ".dll");
var interopAssembly = Assembly.LoadFrom(interopAssemblyFile);
_pluginBaseType = interopAssembly.GetType(typeof(PluginBase).FullName);
}
/// <summary>
/// Returns the name and assembly name of qualifying plugin classes found in assemblies within the designated plugin directory.
/// </summary>
/// <returns>An <see cref="IEnumerable{TypeLocator}"/> that represents the qualifying plugin types.</returns>
public static IEnumerable<TypeLocator> FindPlugins()
{
AppDomain domain = null;
try
{
domain = AppDomain.CreateDomain("Discovery Domain");
var finder = (PluginFinder)domain.CreateInstanceAndUnwrap(typeof(PluginFinder).Assembly.FullName, typeof(PluginFinder).FullName);
return finder.Find();
}
finally
{
if (domain != null)
{
AppDomain.Unload(domain);
}
}
}
/// <summary>
/// Surveys the configured plugin path and returns the the set of types that qualify as plugin classes.
/// </summary>
/// <remarks>
/// Since this method loads assemblies, it must be called from within a dedicated application domain that is subsequently unloaded.
/// </remarks>
private IEnumerable<TypeLocator> Find()
{
var result = new List<TypeLocator>();
foreach (var file in Directory.GetFiles(Path.GetFullPath(PluginPath), "*.dll"))
{
try
{
var assembly = Assembly.LoadFrom(file);
foreach (var type in assembly.GetExportedTypes())
{
if (!type.Equals(_pluginBaseType) &&
_pluginBaseType.IsAssignableFrom(type))
{
result.Add(new TypeLocator(assembly.FullName, type.FullName));
}
}
}
catch (Exception e)
{
// Ignore DLLs that are not .NET assemblies.
}
}
return result;
}
}
/// <summary>
/// Encapsulates the assembly name and type name for a <see cref="Type"/> in a serializable format.
/// </summary>
[Serializable]
internal class TypeLocator
{
/// <summary>
/// Initializes a new instance of the <see cref="TypeLocator"/> class.
/// </summary>
/// <param name="assemblyName">The name of the assembly containing the target type.</param>
/// <param name="typeName">The name of the target type.</param>
public TypeLocator(
string assemblyName,
string typeName)
{
if (string.IsNullOrEmpty(assemblyName)) throw new ArgumentNullException("assemblyName");
if (string.IsNullOrEmpty(typeName)) throw new ArgumentNullException("typeName");
AssemblyName = assemblyName;
TypeName = typeName;
}
/// <summary>
/// Gets the name of the assembly containing the target type.
/// </summary>
public string AssemblyName { get; private set; }
/// <summary>
/// Gets the name of the target type.
/// </summary>
public string TypeName { get; private set; }
}
互操作程序集包含将实现插件功能的类的基类(请注意,它也派生自 MarshalByRefObject
。
此程序集还定义了 IHost
接口(interface),使插件能够回调到主机应用程序。
/// <summary>
/// Defines the interface common to all untrusted plugins.
/// </summary>
public abstract class PluginBase : MarshalByRefObject
{
public abstract void Initialize(IHost host);
public abstract void SaySomething();
public abstract void DoSomethingDangerous();
public abstract void CallBackToHost();
}
/// <summary>
/// Defines the interface through which untrusted plugins automate the host.
/// </summary>
public interface IHost
{
void SaySomething();
}
最后,每个插件都派生自互操作程序集中定义的基类并实现其抽象方法。任何插件程序集中可能有多个继承类,并且可能有多个插件程序集。
public class Plugin : PluginBase
{
private IHost _host;
public override void Initialize(
IHost host)
{
_host = host;
}
public override void SaySomething()
{
Console.WriteLine("This is a message issued by type: {0}", GetType().FullName);
}
public override void DoSomethingDangerous()
{
var x = File.ReadAllText(@"C:\Test.txt");
}
public override void CallBackToHost()
{
_host.SaySomething();
}
}
关于.net - 寻找一种实用的沙盒 .NET 插件方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4145713/
我想了解 Ruby 方法 methods() 是如何工作的。 我尝试使用“ruby 方法”在 Google 上搜索,但这不是我需要的。 我也看过 ruby-doc.org,但我没有找到这种方法。
Test 方法 对指定的字符串执行一个正则表达式搜索,并返回一个 Boolean 值指示是否找到匹配的模式。 object.Test(string) 参数 object 必选项。总是一个
Replace 方法 替换在正则表达式查找中找到的文本。 object.Replace(string1, string2) 参数 object 必选项。总是一个 RegExp 对象的名称。
Raise 方法 生成运行时错误 object.Raise(number, source, description, helpfile, helpcontext) 参数 object 应为
Execute 方法 对指定的字符串执行正则表达式搜索。 object.Execute(string) 参数 object 必选项。总是一个 RegExp 对象的名称。 string
Clear 方法 清除 Err 对象的所有属性设置。 object.Clear object 应为 Err 对象的名称。 说明 在错误处理后,使用 Clear 显式地清除 Err 对象。此
CopyFile 方法 将一个或多个文件从某位置复制到另一位置。 object.CopyFile source, destination[, overwrite] 参数 object 必选
Copy 方法 将指定的文件或文件夹从某位置复制到另一位置。 object.Copy destination[, overwrite] 参数 object 必选项。应为 File 或 F
Close 方法 关闭打开的 TextStream 文件。 object.Close object 应为 TextStream 对象的名称。 说明 下面例子举例说明如何使用 Close 方
BuildPath 方法 向现有路径后添加名称。 object.BuildPath(path, name) 参数 object 必选项。应为 FileSystemObject 对象的名称
GetFolder 方法 返回与指定的路径中某文件夹相应的 Folder 对象。 object.GetFolder(folderspec) 参数 object 必选项。应为 FileSy
GetFileName 方法 返回指定路径(不是指定驱动器路径部分)的最后一个文件或文件夹。 object.GetFileName(pathspec) 参数 object 必选项。应为
GetFile 方法 返回与指定路径中某文件相应的 File 对象。 object.GetFile(filespec) 参数 object 必选项。应为 FileSystemObject
GetExtensionName 方法 返回字符串,该字符串包含路径最后一个组成部分的扩展名。 object.GetExtensionName(path) 参数 object 必选项。应
GetDriveName 方法 返回包含指定路径中驱动器名的字符串。 object.GetDriveName(path) 参数 object 必选项。应为 FileSystemObjec
GetDrive 方法 返回与指定的路径中驱动器相对应的 Drive 对象。 object.GetDrive drivespec 参数 object 必选项。应为 FileSystemO
GetBaseName 方法 返回字符串,其中包含文件的基本名 (不带扩展名), 或者提供的路径说明中的文件夹。 object.GetBaseName(path) 参数 object 必
GetAbsolutePathName 方法 从提供的指定路径中返回完整且含义明确的路径。 object.GetAbsolutePathName(pathspec) 参数 object
FolderExists 方法 如果指定的文件夹存在,则返回 True;否则返回 False。 object.FolderExists(folderspec) 参数 object 必选项
FileExists 方法 如果指定的文件存在返回 True;否则返回 False。 object.FileExists(filespec) 参数 object 必选项。应为 FileS
我是一名优秀的程序员,十分优秀!