web-dev-qa-db-ja.com

.NET 4.0で、メモリ内アセンブリを「サンドボックス化」してメソッドを実行するにはどうすればよいですか?

この質問が行われた理由は次のとおりです: www.devplusplus.com/Tests/CSharp/Hello_World

以前にも同様の質問がありましたが、オンラインの多くの回答にはいくつかの問題があります。

  1. これは、レガシーモードではなく、「。Net4.0」スタイルで実行する必要があります。
  2. アセンブリはメモリ内にあり、メモリ内にのみ存在します。ファイルシステムに書き込むことはできません。
  3. ファイルシステム、ネットワークなどへのすべてのアクセスを制限したいと思います。

このようなもの:

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

これまでのところ、AppDomainを作成してアセンブリをロードする方法が見つかりませんTHAT IS NOT ON THE FILE SYSTEM 、むしろ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);

では、なぜ最初にアセンブリをファイルに保存できないのですか?

2つの理由:

  1. このコードは、ファイルシステム自体へのアクセスが制限された共有Webサーバー上にあります。
  2. このコードは潜在的に何千回も実行する必要があるかもしれません、そして私は一時的であっても1,000のdllを望んでいません。
47
Timothy Khouri

OK、まず最初に: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の外部でコンパイルプロセスを引き上げることができることに注意してください。これは私の意見ではプラスです。

参考までに、アクセス許可が制限されており、必要に応じて簡単にアンロードできる、すっきりとした別の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のセキュリティ証拠を提供する場合は、アセンブリに署名して強力な名前を付ける必要があります。

これはインプロセスで実行すると正常に機能することに注意してください。ただし、防弾スクリプト環境が本当に必要な場合は、さらに一歩進んでスクリプトを別のプロセスに分離し、悪意のある(または単に愚かな)ことを実行するスクリプトを確保する必要がありますスタックオーバーフロー、フォーク爆弾、メモリ不足の状況のように、アプリケーションプロセス全体がダウンすることはありません。必要に応じて、それを行うための詳細情報を提供できます。

42
MikeP