在.NET 4.0中,如何“沙箱”内存中的程序集并执行方法?

时间:2011-05-13 21:31:30

标签: c# security .net-4.0 appdomain

以下是提出此问题的原因:www.devplusplus.com/Tests/CSharp/Hello_World

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

  1. 这必须以“.Net 4.0”风格完成,而不是传统模式。
  2. 程序集在内存中,只在内存中,不能写入文件系统。
  3. 我想限制对文件系统,网络等的所有访问。
  4. 这样的事情:

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

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

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

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

    显示安全漏洞的示例代码

    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. 此代码可能需要运行数千次,而且我不想要1,000个dll,即使是暂时的。

1 个答案:

答案 0 :(得分:42)

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

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

由于框架已经写入文件系统,因此解决方法是将程序集写入临时文件,然后根据需要加载。但是,当您加载它时,您需要在AppDomain中临时断言权限,因为您不允许访问文件系统。这是一个示例片段:

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炸弹和内存不足等情况并不会导致整个应用程序进程崩溃。如果您需要,我可以为您提供更多相关信息。