【问题标题】:In .NET 4.0, how do I 'sandbox' an in-memory assembly and execute a method?在 .NET 4.0 中,我如何“沙箱化”内存中的程序集并执行方法?
【发布时间】:2011-08-25 06:36:22
【问题描述】:

这就是提出这个问题的原因:www.devplusplus.com/Tests/CSharp/Hello_World

虽然之前有人问过类似的问题,但网上的许多答案都有几个问题:

  1. 这必须是“.Net 4.0”风格,而不是传统模式。
  2. 程序集在内存中并且只会在内存中,它不能写入文件系统。
  3. 我想限制对文件系统、网络等的所有访问。

类似这样的:

    var evidence = new Evidence();
    evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
    var permissionSet = SecurityManager.GetStandardSandbox(evidence);

到目前为止,我找不到创建 AppDomain 和加载程序集的方法不在文件系统上,而是在 RAM 中。

同样,上面确定了其他解决方案不起作用的原因:1. 许多是针对 4.0 之前的版本,以及 2. 许多依赖于指向文件系统的“.Load”方法。

答案 2:我有一个程序集引用,因为它是由 CSharpCodeProvider 类生成的,所以如果您知道将 that 转换为字节数组的方法,那就完美了!

显示安全漏洞的示例代码

var provider = new CSharpCodeProvider(new Dictionary<String, String>
    { { "CompilerVersion", "v4.0" } });

var compilerparams = new CompilerParameters
    { GenerateExecutable = false, GenerateInMemory = true, };

var compilerResults = provider.CompileAssemblyFromSource(compilerparams,
    string_Of_Code_From_A_User);

var instanceOfSomeClass = compilerResults.CompiledAssembly
    .CreateInstance(className);

// The 'DoSomething' method can write to the file system and I don't like that!
instanceOfSomeClass.GetType().GetMethod("DoSomething")
    .Invoke(instanceOfSomeClass, null);

那么为什么我不能先将程序集保存到文件中呢?

有两个原因:

  1. 此代码位于共享 Web 服务器上,对文件系统本身的权限有限。
  2. 此代码可能需要运行数千次,我不想要 1000 个 dll,即使是暂时的。

【问题讨论】:

  • 就像你想用一个字节数组加载?或者您的意思是您正在使用 emit API 创建一个内存中的?
  • @tyranid 这是一个非常好的问题,我想我有点假设程序集是从可以转换为字节数组的东西加载的。如果它是动态发出的,发出程序集的代码可能需要在其他应用程序域中运行。
  • 我以前曾解决过这个问题,但如果不将程序集写入磁盘,我找不到获取 COFF 字节 [] 的方法。如果您有访问问题,您也许可以使用隔离存储。或者,可能有一些方法可以设置写入内存的虚拟文件路径,但我不确定。
  • 您不能将程序集保存到磁盘,然后以降低的权限将其加载到自己的 appdomain 中吗?将程序集保存到磁盘不会引入安全问题,将其加载回未选中会。
  • @Timothy,为什么不在临时位置创建程序集,然后在沙箱中执行它?您特别希望它在 RAM 中的原因是什么?

标签: c# security .net-4.0 appdomain


【解决方案1】:

好的,首先要做的是:没有实际的方法可以使用 CSharpCodeProvider 完全在内存中对 C# 源代码进行动态编译。有些方法似乎支持该功能,但由于 C# 编译器是不能在进程内运行的本机可执行文件,源字符串保存到临时文件,在该文件上调用编译器,然后生成的程序集是保存到磁盘,然后使用 Assembly.Load 为您加载。

其次,正如您所发现的,您应该能够使用 AppDomain 中的 Compile 方法来加载程序集并为其提供所需的权限。我遇到了同样的异常行为,经过大量挖掘发现这是框架中的一个错误。我在MS Connect 上提交了问题报告。

由于框架已经在写入文件系统,解决方法是将程序集写入临时文件,然后根据需要加载。但是,当您加载它时,您需要在 AppDomain 中临时声明权限,因为您已禁止访问文件系统。这是一个示例 sn-p:

new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();

从那里您可以使用程序集和反射来调用您的方法。请注意,此方法可让您将编译过程提升到沙盒 AppDomain 之外,我认为这是一个加分项。

作为参考,这是我创建的 Sandbox 类,以方便在一个漂亮干净的独立 AppDomain 中启动脚本程序集,该 AppDomain 具有有限的权限并且可以在必要时轻松卸载:

class Sandbox : MarshalByRefObject
{
    const string BaseDirectory = "Untrusted";
    const string DomainName = "Sandbox";

    public Sandbox()
    {
    }

    public static Sandbox Create()
    {
        var setup = new AppDomainSetup()
        {
            ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory),
            ApplicationName = DomainName,
            DisallowBindingRedirects = true,
            DisallowCodeDownload = true,
            DisallowPublisherPolicy = true
        };

        var permissions = new PermissionSet(PermissionState.None);
        permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
        permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

        var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions,
            typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>());

        return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap();
    }

    public string Execute(string assemblyPath, string scriptType, string method, params object[] parameters)
    {
        new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
        var assembly = Assembly.LoadFile(assemblyPath);
        CodeAccessPermission.RevertAssert();

        Type type = assembly.GetType(scriptType);
        if (type == null)
            return null;

        var instance = Activator.CreateInstance(type);
        return string.Format("{0}", type.GetMethod(method).Invoke(instance, parameters));
    }
}

快速提示:如果您使用此方法为新的 AppDomain 提供安全证据,则需要对您的程序集进行签名以赋予其强名称。

请注意,这在进程中运行时可以正常工作,但如果您真的想要一个防弹脚本环境,您需要更进一步,将脚本隔离在一个单独的进程中,以确保执行恶意(或只是愚蠢的)堆栈溢出、fork 炸弹和内存不足等情况不会影响整个应用程序进程。如果您需要,我可以为您提供更多信息。

【讨论】:

  • 好吧,我还是有点迷茫……如果我可以访问断言文件系统权限,我调用的实例类不也有同样的权限吗?
  • 没有。您正在主程序集中调用 Execute() ,显然它有权运行它想要的任何东西。您加载的脚本程序集具有减少的权限,因此它不能自己断言它们。自己试试看很容易。
  • @MikeP - 我的沙箱是一个控制台应用程序。我该如何签名?我收到错误“A null strongname..”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 2014-11-17
  • 2011-03-29
  • 1970-01-01
  • 2011-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多