方法 : サンドボックスで部分信頼コードを実行する

サンドボックス化は、制限されたセキュリティ環境でコードを実行する方法です。この方法を採用すると、コードに付与されるアクセス許可が制限されます。 たとえば、完全に信頼していない発行元のマネージ ライブラリは、完全信頼として実行しないようにする必要があります。 代わりに、必要なアクセス許可 (Execution アクセス許可など) のみを与えるサンドボックスにコードを配置します。

また、部分的に信頼された環境で実行する予定のコードについて、サンドボックスを使用してテストすることもできます。

AppDomain を使用すると、マネージ アプリケーションのサンドボックスを効果的に提供できます。 部分信頼コードの実行時に使用するアプリケーション ドメインには、AppDomain 内で実行する際に利用できる保護対象リソースを定義するアクセス許可が与えられます。 AppDomain 内で実行されるコードは、AppDomain に関連付けられているアクセス許可によって制約され、指定したリソースにのみアクセスできます。 また、AppDomain には、完全信頼として読み込むアセンブリを特定する StrongName 配列も含まれます。 これによって AppDomain の作成者は、新しいサンドボックス化されたドメインを開始して、特定のヘルパー アセンブリを完全信頼とすることができます。 アセンブリを完全信頼として読み込むには、グローバル アセンブリ キャッシュに配置する方法もあります。ただし、そのコンピューター上に作成されたすべてのアプリケーション ドメインに、アセンブリが完全信頼として読み込まれます。 厳密な名前の一覧は AppDomain ごとに決定できるので、対象アセンブリのより限定的な指定が可能になります。

サンドボックスで実行するアプリケーションのアクセス許可セットを指定するには、AppDomain.CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[]) メソッド オーバーロードを使用します。 このオーバーロードを使用することで、コード アクセス セキュリティのレベルを希望どおりに設定できます。 このオーバーロードを使用して AppDomain に読み込まれるアセンブリには、指定の許可セットのみを付与することもできますし、完全信頼を付与することもできます。 グローバル アセンブリ キャッシュ内のアセンブリ、または fullTrustAssemblies (StrongName) 配列パラメーターに列挙されているアセンブリには、完全信頼が付与されます。 完全に信頼できるアセンブリだけを fullTrustAssemblies リストに追加する必要があります。

オーバーロードは次のシグネチャを持ちます。

AppDomain.CreateDomain( string friendlyName,
                        Evidence securityInfo,
                        AppDomainSetup info,
                        PermissionSet grantSet,
                        params StrongName[] fullTrustAssemblies);

CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[]) メソッド オーバーロードのパラメーターでは、AppDomain の名前、AppDomain の証拠、サンドボックスのアプリケーション ベースを指定する AppDomainSetup オブジェクト、使用するアクセス許可セット、および完全に信頼されたアセンブリの厳密な名前を指定します。

セキュリティ上の理由から、info パラメーターに指定するアプリケーション ベースに、ホスト アプリケーションのアプリケーション ベースを指定することはできません。

grantSet パラメーターには、明示的に作成したアクセス許可セットを指定するか、GetStandardSandbox メソッドによって作成された標準アクセス許可セットを指定します。

他の多くの AppDomain 読み込みとは異なり、部分的に信頼されたアセンブリの許可セットの決定に (securityInfo パラメーターで指定される) AppDomain の証拠は使用されません。 代わりに、grantSet パラメーターによって単独で指定されます。 ただし、分離ストレージのスコープの決定など、他の用途に証拠を使用できます。

サンドボックスでアプリケーションを実行するには

  1. 信頼関係のないアプリケーションに付与するアクセス許可セットを作成します。 付与できる最小限のアクセス許可は Execution です。 また、信頼関係のないコードでも安全と考えられる追加のアクセス許可を付与することもできます。たとえば、IsolatedStorageFilePermission などです。 次のコードでは、Execution アクセス許可のみを含む新しいアクセス許可セットを作成します。

    PermissionSet permSet = new PermissionSet(PermissionState.None);
    permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
    

    また、"Internet" など、既存の名前付きアクセス許可セットを使用することもできます。

    Evidence ev = new Evidence();
    ev.AddHostEvidence(new Zone(SecurityZone.Internet));
    PermissionSet internetPS = SecurityManager.GetStandardSandbox(ev);
    

    証拠のゾーンに応じて、GetStandardSandbox メソッドは Internet アクセス許可セットまたは LocalIntranet アクセス許可セットを返します。 また、GetStandardSandbox は、参照として渡される一部の証拠オブジェクトの ID アクセス許可を構築します。

  2. 信頼関係のないコードを呼び出すホスト クラス (この例では Sandboxer) を含むアセンブリに署名します。 アセンブリの署名で使用した StrongName を、CreateDomain を呼び出す際の fullTrustAssemblies パラメーターに StrongName 配列として追加します。 部分信頼コードを実行できるようにする場合、または部分信頼アプリケーションにサービスを提供する場合は、ホスト クラスを完全信頼として実行する必要があります。 アセンブリの StrongName を読み取る方法は次のとおりです。

    StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();
    

    mscorlib や System.dll などの .NET Framework アセンブリを完全信頼一覧に追加することはできません。グローバル アセンブリ キャッシュから完全信頼として読み込まれているためです。

  3. CreateDomain メソッドの AppDomainSetup パラメーターを初期化します。 このパラメーターを使用すると、新しい AppDomain の多くの設定を制御できます。 ApplicationBase プロパティは重要な設定なので、ホスト アプリケーションの AppDomainApplicationBase プロパティとは違う設定にする必要があります。 ApplicationBase 設定が同じ場合、部分的に信頼されたアプリケーションは、ホスト アプリケーションが定義する例外を完全信頼として読み込み、それを悪用できるようになります。 これは、キャッシュ (例外) が推奨されないもう 1 つの理由です。 ホストのアプリケーション ベースと、サンドボックス アプリケーションのアプリケーション ベースを異なる設定にすると、悪用のリスクが軽減されます。

    AppDomainSetup adSetup = new AppDomainSetup();
    adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
    
  4. CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, StrongName[]) メソッド オーバーロードを呼び出し、指定したパラメーターを使用してアプリケーション ドメインを作成します。

    このメソッドのシグネチャは次のとおりです。

    public static AppDomain CreateDomain(string friendlyName, 
        Evidence securityInfo, AppDomainSetup info, PermissionSet grantSet, 
        params StrongName[] fullTrustAssemblies)
    

    追加情報:

    • これは、パラメーターとして PermissionSet を使用する CreateDomain メソッドの唯一のオーバーロードです。つまり、部分信頼設定でアプリケーションを読み込むことができる唯一のオーバーロードになります。

    • evidence パラメーターは、アクセス許可セットの計算では使用されません。このパラメーターは、.NET Framework の他の機能が識別目的で使用します。

    • このオーバーロードでは、info パラメーターの ApplicationBase プロパティを必ず設定します。

    • fullTrustAssemblies パラメーターには params キーワードがあります。これは StrongName 配列を作成する必要がないことを示します。 パラメーターとして、0 個、1 個、または複数の厳密な名前を渡すことができます。

    • アプリケーション ドメインを作成するコードは次のとおりです。

    AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
    
  5. 作成したサンドボックスの AppDomain にコードを読み込みます。 これは、次の 2 つの方法で行うことができます。

    2 番目の方が望ましい方法です。新しい AppDomain インスタンスにパラメーターを渡しやすいためです。 CreateInstanceFrom メソッドには 2 つの重要な機能があります。

    • アセンブリが保存されていない場所を示すコード ベースを使用できる。

    • Assert の下で、完全信頼 (PermissionState.Unrestricted) で作成操作を実行できます。こうすることで、重要なクラスのインスタンスを作成できます (この状況は、アセンブリに透過マーキングがなく、完全信頼として読み込まれるときに毎回発生します)。 そのため、この関数で信頼するコードのみを作成する場合は注意が必要です。新しいアプリケーション ドメインに、完全信頼クラスのインスタンスのみを作成することをお勧めします。

    ObjectHandle handle = Activator.CreateInstanceFrom(
    newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
           typeof(Sandboxer).FullName );
    

    新しいドメインにクラスのインスタンスを作成するには、そのクラスで MarshalByRefObject クラスを拡張する必要があります。

    class Sandboxer:MarshalByRefObject
    
  6. 新しいドメイン インスタンスをこのドメインの参照にラップ解除します。 この参照は、信頼関係のないコードを実行するときに使用されます。

    Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
    
  7. 作成した Sandboxer クラスのインスタンスで、ExecuteUntrustedCode メソッドを呼び出します。

    newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
    

    この呼び出しは、アクセス許可が制限されているサンドボックス アプリケーション ドメインで実行されます。

    public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
        {
            //Load the MethodInfo for a method in the new assembly. This might be a method you know, or 
            //you can use Assembly.EntryPoint to get to the entry point in an executable.
            MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
            try
            {
                // Invoke the method.
                target.Invoke(null, parameters);
            }
            catch (Exception ex)
            {
            //When information is obtained from a SecurityException extra information is provided if it is 
            //accessed in full-trust.
                (new PermissionSet(PermissionState.Unrestricted)).Assert();
                Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
    CodeAccessPermission.RevertAssert();
                Console.ReadLine();
            }
        }
    

    System.Reflection を使用して、部分的に信頼されたアセンブリでメソッドのハンドルを取得します。 このハンドルは、最小限のアクセス許可を持つ安全な方法でコードを実行するために使用できます。

    上記のコードでは、SecurityException を出力する前に完全信頼のアクセス許可の Assert があることがわかります。

    new PermissionSet(PermissionState.Unrestricted)).Assert()
    

    完全信頼アサートは、SecurityException から拡張情報を取得するために使用します。 Assert を使用しない場合、SecurityExceptionToString メソッドは、スタック上に部分的に信頼されたコードがあることを検出します。また、返される情報を制限します。 部分信頼コードがその情報を読み取ることができる場合、これによってセキュリティ上の問題が発生する可能性もありますが、UIPermission を付与しないことでそのリスクは軽減されます。 完全信頼アサートは慎重に使用する必要があります。部分信頼コードが完全信頼に昇格できないようにする場合にのみ使用します。 通常、信頼できないコードは、同じ関数で呼び出すことも、完全信頼のアサートを呼び出した後に呼び出すことも避ける必要があります。 アサートの使用を完了したときは、常にアサートを元に戻すことをお勧めします。

使用例

次の例では、前のセクションの手順を実装しています。 この例では、Visual Studio ソリューションの Sandboxer というプロジェクトにも UntrustedCode というプロジェクトが含まれます。これはクラス UntrustedClass を実装します。 このシナリオでは、指定した数字がフィボナッチ数かどうかを示すために、true または false を返すことが期待されているメソッドを含むライブラリ アセンブリをダウンロードしたという前提です。 代わりに、このメソッドはコンピューターからのファイルの読み込みを試みます。 次の例は信頼関係のないコードを示します。

using System;
using System.IO;
namespace UntrustedCode
{
    public class UntrustedClass
    {
        // Pretend to be a method checking if a number is a Fibonacci
        // but which actually attempts to read a file.
        public static bool IsFibonacci(int number)
        {
           File.ReadAllText("C:\\Temp\\file.txt");
           return false;
        }
    }
}

次の例は、信頼関係のないコードを実行する Sandboxer アプリケーション コードを示します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Remoting;

//The Sandboxer class needs to derive from MarshalByRefObject so that we can create it in another 
// AppDomain and refer to it from the default AppDomain.
class Sandboxer : MarshalByRefObject
{
    const string pathToUntrusted = @"..\..\..\UntrustedCode\bin\Debug";
    const string untrustedAssembly = "UntrustedCode";
    const string untrustedClass = "UntrustedCode.UntrustedClass";
    const string entryPoint = "IsFibonacci";
    private static Object[] parameters = { 45 };
    static void Main()
    {
        //Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder 
        //other than the one in which the sandboxer resides.
        AppDomainSetup adSetup = new AppDomainSetup();
        adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);

        //Setting the permissions for the AppDomain. We give the permission to execute and to 
        //read/discover the location where the untrusted code is loaded.
        PermissionSet permSet = new PermissionSet(PermissionState.None);
        permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

        //We want the sandboxer assembly's strong name, so that we can add it to the full trust list.
        StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();

        //Now we have everything we need to create the AppDomain, so let's create it.
        AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);

        //Use CreateInstanceFrom to load an instance of the Sandboxer class into the
        //new AppDomain. 
        ObjectHandle handle = Activator.CreateInstanceFrom(
            newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
            typeof(Sandboxer).FullName
            );
        //Unwrap the new domain instance into a reference in this domain and use it to execute the 
        //untrusted code.
        Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
        newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
    }
    public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
    {
        //Load the MethodInfo for a method in the new Assembly. This might be a method you know, or 
        //you can use Assembly.EntryPoint to get to the main function in an executable.
        MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
        try
        {
            //Now invoke the method.
            bool retVal = (bool)target.Invoke(null, parameters);
        }
        catch (Exception ex)
        {
            // When we print informations from a SecurityException extra information can be printed if we are 
            //calling it with a full-trust stack.
            (new PermissionSet(PermissionState.Unrestricted)).Assert();
            Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
            CodeAccessPermission.RevertAssert();
            Console.ReadLine();
        }
    }
}

参照

概念

安全なコーディングのガイドライン