コードアクセスセキュリティの仕組みと設定方法はじめに.NET Frameworkに移行する前、多くの開発者は、Windowsアプリケーションがレジストリ、ファイルシステム、イベントログ、環境変数、プリンタなど、すべてのローカルリソースに自由にアクセスすることを当然のように考えていました。ロールベースのセキュリティ制限という仕組みがあったため、ユーザー(またはアプリケーションを実行しているユーザーコンテキスト)にリソースの使用権限が与えられてさえいれば、アプリケーション実行時にどんなリソースでも自由に使用できるという状況に慣れてしまっていたのです。 しかし、分散コンポーネント主体のシステムが急増した現在、アプリケーションがインターネット/イントラネットサイトやネットワーク共有からコンポーネントをダウンロードして実行することは珍しくなくなりました。そのようなアプリケーションに潜む問題点は明らかです。意図的かどうかは別として、悪意のあるコードが外部からダウンロードされ、ローカルコンピュータや内部ネットワークに大損害を与える可能性があります。また、機密漏洩のようなセキュリティ上の脅威も存在します。 こうした状況で必要とされるのは、アセンブリに付随する何らかの「証拠」に基づいてコードにリソースへのアクセス許可を付与するような統合セキュリティモデルです。.NET Frameworkのコードアクセスセキュリティという機構は、このセキュリティモデルを実現します。本稿では、コードアクセスセキュリティポリシーの定義と設定について説明します。 コードアクセスセキュリティの概要共通言語ランタイム(CLR)に実装されているコードアクセスセキュリティは、アセンブリに関して収集された「証拠」に基づいて動作します。証拠とは具体的に次のようなものを指します。
CLRは、収集した証拠に基づいてアセンブリをコードグループに割り当てます。コードグループは逆ツリー状の階層構造を持ちます。各コードグループは、そのグループに割り当てられるアセンブリを指定するただ1つのメンバシップ条件を持ちます。さらに、そのグループ内のアセンブリに許されているアクションを示す一連のアクセス許可を持ちます。.NET Frameworkをインストールすると、既定のコードグループ、メンバシップ条件、およびアクセス許可が有効になり、これによって、コンピュータやネットワークが悪意のあるコードの被害に遭う可能性が低くなります。コードグループについては、後ほどさらに詳しく説明します。 ポリシーレベル最大4レベルのセキュリティポリシーレベルが存在します。 表1
Enterprise、Machine、Userのポリシーレベルでは、セキュリティポリシー設定情報がXMLベースの設定ファイルから読み込まれます。AppDomainポリシーレベルは既定では有効にならず、プログラムで明示的に指定する必要があります。AppDomainポリシーレベルの実装方法については後述します。Userセキュリティポリシーは特定コンピュータの特定ユーザーにのみ適用されます。Machineセキュリティポリシーは特定コンピュータの全ユーザーに適用されます。Enterpriseセキュリティポリシーは特定のActive Directory環境に所属する一群のコンピュータに適用されます。AppDomainセキュリティポリシーはオペレーティングシステムプロセス内で実行されている特定のアプリケーションにのみ適用されます。 コードグループポリシーレベルごとに固有のコードグループセットが存在します。既定のEnterpriseおよびUserセキュリティポリシーレベルには、それぞれただ1つのコードグループが存在します。EnterpriseおよびUserポリシーレベルでは、すべてのアセンブリがこの「All Code」コードグループに割り当てられ、すべてのコードがすべてのリソースに完全信頼の下で(すなわち、無制限に)アクセスできます。一方、通常のMachineセキュリティポリシーレベルには階層化されたコードグループが存在し、各コードグループによってリソースへの具体的なアクセス許可が付与されます。次の図(図1)は、Machineポリシーレベルにおける典型的なコードグループ階層を示しています。 図1 Machineポリシーレベルのコードグループ階層 ![]() 前述のように、CLRが読み込んだ各アセンブリは、そのアセンブリに関して収集された証拠に基づいてMachineポリシーレベルの1つまたは複数のコードグループに割り当てられます。アセンブリがコードグループに割り当てられるのは、コードグループのメンバシップ条件を満たす場合です。CLRは「All Code」コードグループから開始してツリーをスキャンし、アセンブリをメンバとするすべてのコードグループを探します。コードグループのメンバシップ条件を満たさないアセンブリは、そのグループおよびその下位グループのメンバとされません。.NET Frameworkで使用可能なビルトインのメンバシップ条件を表2に示します。 表2
各コードグループは1つのアクセス許可セットを持ちます。.NET Frameworkには、出荷時にビルトインされた一連のアクセス許可セットがあります。 コードグループに割り当てられたアセンブリには、そのコードグループに対応するアクセス許可が付与されます。アセンブリが特定ポリシーレベルの複数のコードグループに割り当てられた場合は、各コードグループのアクセス許可の和集合が、当該ポリシーレベルにおけるアクセス許可として付与されます。つまり、各コードグループのアクセス許可が追加的に付与されるわけです。 次のケース(図2)で考えてみましょう。この例では、MachineセキュリティポリシーレベルのAll Code、Internet_Zone、Partner_Siteの各コードグループにアセンブリが割り当てられています。 図2 Machineポリシーレベルでのコードグループの割り当て ![]() この例で、上記の3つのコードグループに対応するアクセス許可セットはNothing、Internet、LocalIntranetなので、当該アセンブリのMachineセキュリティポリシーレベルにおけるアクセス許可はAll Code、Internet_Zone、Partner_Siteの3つのコードグループのアクセス許可の和集合となります。ただし、特定のアセンブリで実効性を持つのは、各セキュリティポリシーレベルにおけるアクセス許可の積集合となります。つまり、各セキュリティポリシーレベルで他のレベルのアクセス許可を実効的に「否定」したければ、そのアクセス許可を付与しなければよいわけです。図3は、この様子を示しています。 図3 アクセス許可の積集合 ![]() 既定のEnterpriseおよびUserセキュリティポリシーレベルでは、すべてのアセンブリに「FullTrust」(完全信頼)アクセス許可が付与されるため、通常はMachineポリシーレベルでアセンブリのアクセス許可が決まります(AppDomainは既定では有効になりません)。 セキュリティポリシーの運用さて前置きはこれまでとし、次にセキュリティポリシーの実際の運用方法を見てみましょう。ここではサンプルコードも紹介します。これを実際に試してみれば、セキュリティポリシーを変更したときに、どのような効果が現れるかが分かるでしょう。 Enterprise、Machine、Userの3レベルのセキュリティポリシーを定義しているXMLファイルを編集するには、コマンドラインツールの「caspol.exe」か、MMCスナップインの「mscorcfg.msc」を使います。多数のコンピュータのセキュリティポリシーを変更するスクリプトを作成するのであれば、使用するツールとしては「caspol.exe」の方が向いているでしょう。以下、本稿ではMMCスナップインを使うものとします。このスナップインを実行するには、コマンドプロンプトで次のように入力します。 ’’%Systemroot%’’Microsoft.NETFramework’’version’’Mscorcfg.msc UIが表示されたら、[ランタイムセキュリティポリシーコンピュータコードグループAll_Code]を展開します。現在のコンピュータで定義されているMachineレベルのコードグループの階層構造(図4)が表示されます。 図4 Machineレベルのコードグループ ![]() 既定のEnterpriseおよびUserレベルのコードグループ階層はAll_Codeコードグループしか持たないので、あまり面白くないでしょう。展開可能なコードグループのノードをすべて展開すれば、階層内のすべてのコードグループを表示できます。[LocalIntranet_Zone]コードグループをクリックし、右側のパネルの[コード グループ プロパティの編集]リンクをクリックしてください。[LocalIntranet_Zoneのプロパティ]ダイアログボックスが表示されたら、[アクセス許可セット]タブをクリックします。LocalIntranet_Zoneコードグループに付与されているアクセス許可の一覧が表示されます。 図5 LocalIntranet_Zoneのアクセス許可 ![]() 既定でLocalIntranet_Zoneコードグループには複数のリソース(レジストリ、ファイルシステムなど)のアクセス許可は設定されていません。ここをクリックすると、.NET Frameworkコードアクセスセキュリティのすべてのアクセス許可が表示されます。 [メンバシップ条件]タブをクリックすると、メンバシップ条件の種類は[ゾーン]で、[イントラネット]が特定ゾーンとなっていることが分かります。既定では、組織のイントラネットに属するアセンブリと、UNCパスから読み込まれたアセンブリが、このメンバシップ条件を満たします。[インターネット]、[ローカルイントラネット]、[信頼されているサイト]、[制限されているサイト]の4つのゾーンをInternet Explorerの[インターネットオプション]ダイアログで設定することができます。 次のサンプルコードは、コードアクセスセキュリティのアクセス許可がマネージコードの実行にどう作用するかを示すものです。このコンソールアプリケーションは、レジストリキーHKLMSoftwareMicrosoft.NetFrameworkの下のレジストリサブキーを読み込み、そのキーの名前をコンソールに表示します。 using System; namespace ConsoleReadRegistry { /// <summary> /// Summary description for Class1. /// </summary> class Class1 { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { Microsoft.Win32.RegistryKey rk; try { rk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "SoftwareMicrosoft.NetFramework",false); string[] skNames = rk.GetSubKeyNames(); for (int i=0;i<skNames.Length;++i) { Console.WriteLine("Registry Key: {0}", skNames[i]); } rk.Close(); } catch(System.Security.SecurityException e) { Console.WriteLine("Security Exception Encountered: {0}", e.Message); } } } } このアプリケーションをコンパイルして実行すると、.NetFrameworkキーの下にあるレジストリキーの名前が表示されます。つまり、このアセンブリにはレジストリの実行と読み込みのアクセス許可が付与されているわけです。 図6 ![]() 何が起きたか振り返ってみましょう。このコードをコンパイルすると、アセンブリがハードディスク上に生成されます。UserおよびEnterpriseセキュリティポリシーレベルに既定のセキュリティポリシーが設定されていて、このアセンブリはそこでAll_Codeコードグループに割り当てられるものと仮定します。この2つのポリシーレベルでは、All_Codeコードグループに割り当てられるすべてのアセンブリに「FullTrust」アクセス許可が付与されます。 このアセンブリはローカルハードディスクから読み込まれたので、MachineポリシーレベルでAll_CodeおよびMy_Computer_Zoneコードグループのメンバシップ条件を満たします。従って、このポリシーレベルではアセンブリに2つのコードグループのアクセス許可の和集合が付与されます。図1から分かるように、MachineポリシーレベルのAll_Codeコードグループには対応するアクセス許可はなく、My_Computer_Zoneコードグループに対応するアクセス許可は「FullTrust」(無制限)です。つまり、アセンブリには、Machineポリシーレベルで「FullTrust」アクセス許可が付与されることになります。 Enterprise、Machine、Userの3つのセキュリティポリシーレベルでは、収集された証拠に基づいてアクセス許可セットが付与されます。各ポリシーレベルで付与されるアクセス許可の積集合によって、アセンブリに付与されるアクセス許可が決まります(図3を参照)。この例ではAppDomainポリシーレベルをプログラムで指定していないので、このレベルは適用されません(AppDomainポリシーレベルを有効にする方法については後述)。以上のシナリオにより、アセンブリにはすべてのレベルで「FullTrust」アクセス許可が付与され、ローカルリソースを無制限にアクセスできるようになります。 同じアセンブリを共有フォルダにコピーし、コマンドラインからUNCパスを入力してアプリケーションを実行した場合は、図6とは異なる結果が得られます。例えば、コマンドプロンプトでMyMachineMyShareConsoleReadRegistry.exeと入力してアプリケーションを開始すると、次のように表示されます。 図7 ![]() 先ほどの例との違いは、アセンブリがUNCパスで読み込まれることです。既定の[ローカルイントラネットゾーン]設定では、UNCパスで読み込まれたすべてのアセンブリをLocalIntranet_Zoneコードグループに割り当てるものと規定されています。仮にファイル共有がローカルコンピュータ上に置かれていたとしても、このアセンブリはMy_Computer_Zoneコードグループには割り当てられません。つまり、Machineポリシーレベルでは、アセンブリに図5のアクセス許可(「Registry」アクセス許可は含まれない)が付与されることになります。従って、次のメソッド呼び出しはSecurityExceptionを発生させます。
rk = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
"SoftwareMicrosoft.NetFramework",false);
特定のファイル共有に置かれたアセンブリにレジストリ読み込みのアクセス許可を付与するには、どうすればよいでしょう。簡単なのは、設定ツールでLocalIntranet_Zoneコードグループに「FullTrust」または「Everything」アクセス許可セットを関連付けることですが、これだとコンピュータが攻撃されやすくなるので最善の方法とはいえません。それよりも、「Registry」アクセス許可を持つカスタムコードグループをMachineポリシーレベルに追加する方がよいでしょう。このコードグループのURLメンバシップ条件で、ファイル共有のUNCパスを指定するわけです。では、設定ツールを使用してカスタムコードグループをLocalIntranet_Zoneコードグループの子として追加してみましょう。 まず、レジストリのアクセス許可を持つカスタムアクセス許可セットを作成します。MMCスナップインを使用し、[ランタイムセキュリティポリシーコンピュータアクセス許可セット]ノードを右クリックし、ポップアップメニューから[新規作成]を選択します。[アクセス許可セットの作成]ダイアログが表示されたら、[アクセス許可セットの新規作成]ラジオボタンを選択し、新しいアクセス許可セットの名前と説明を入力します。 図8 ![]() [次へ]をクリックします。次のダイアログが表示されたら、リストから[レジストリ]アクセス許可を選択し、[追加]をクリックします。[アクセス許可の設定]ダイアログが表示されたら、[アセンブリにレジストリへの無制限のアクセスを許可する]ラジオボタンを選択します。[OK]をクリックし、[完了]をクリックします。 新規のコードグループを作成するには、[ランタイムセキュリティポリシーコンピュータコードグループAll_CodeLocalIntranet_Zone]ノードを右クリックし、ポップアップメニューから[新規作成]を選択します。新しいコードグループの名前と説明を入力し、[次へ]をクリックします。[条件の種類を選択する]ダイアログが表示されたら、ドロップダウンリストから条件の種類として[URL]を選択し、当該アセンブリのUNCパスを入力します。[次へ]をクリックします。 図9 ![]() [コードグループにアクセス許可セットを割り当てる]ダイアログが表示されたら、新しく作成したアクセス許可セットをドロップダウンリストから選択します。[次へ]と[完了]をクリックします。 この段階で、当該アセンブリは、All_Code、LocalIntranet_Zone、および新しく作成されたコードグループのメンバシップ条件を満たします。また、LocalIntranet_Zoneによるアクセス許可セットと新しいコードグループで付与されたレジストリアクセス許可との和が、Machineポリシーレベルで付与されるアクセス許可となります。UNCパスを入力してアプリケーションを再度実行すると、今度はSecurityExceptionは発生せず、当該コードはレジストリの読み込みを許可されます。 AppDomainセキュリティポリシーレベルAppDomainセキュリティポリシーレベルは、既定では有効になりません。ここまでは、Enterprise、Machine、Userの各ポリシーレベルをMMCスナップインで設定する方法を見てきました。AppDomainポリシーレベルを使用するには、実行時にコードから設定します。これが実装されるのは、アセンブリをアプリケーションドメインに動的に読み込む場合に限られます(これをサンドボックス化と呼ぶこともあります)。AppDomainセキュリティポリシーを実効的に作用させるには、アセンブリをAppDomainに読み込む前に、AppDomainポリシーレベルをプログラムで定義しておく必要があります。次の例を見てみましょう。 using System; using System.Net; using System.Diagnostics; using System.Threading; using System.Security; using System.Security.Policy; using System.Security.Permissions; namespace ConfigPolicy { /// <summary> /// Summary description for Class1. /// </summary> class SetAppDomainPolicy { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { // Create a new AppDomain PolicyLevel. PolicyLevel domainPolicy = PolicyLevel.CreateAppDomainLevel(); // Create a ’Membership Condition’ // to be assigned to a new code group AllMembershipCondition allCodeMC = new AllMembershipCondition(); // Create a new permission set with the same permissions // as the "LocalIntranet" permission set. PermissionSet CustomPS = new Per issiionSet( domainPolicy.GetNamedPermissionSet("LocalIntranet")); // Add the permission needed to read from the registry. CustomPS.AddPermission(new RegistryPermission( PermissionState.Unrestricted)); PolicyStatement polState = new PolicyStatement(CustomPS); // Create a new code group which will serve as the root code // group for the AppDomain policy level CodeGroup allCodeCG = new UnionCodeGroup(allCodeMC,polState); domainPolicy.RootCodeGroup = allCodeCG; // Create a new application domain. AppDomain domain = System.AppDomain.CreateDomain("CustomDomain"); domain.SetAppDomainPolicy(domainPolicy); // Load and execute the assembly. try { string FQ_UNC_Path = @"MyServerMyShareConsoleReadRegistry.exe"; domain.ExecuteAssembly(FQ_UNC_Path); } catch(PolicyException e) { Console.WriteLine("PolicyException: {0}", e.Message); } catch(Exception e) { Console.WriteLine( "Unexpected Exception: {0}", e.Message); } AppDomain.Unload(domain); } } } このシナリオでは、カスタムアクセス許可セットを、既定のLocalIntranetアクセス許可セットと同じアクセス許可で作成します。LocalIntranetアクセス許可セットにはアセンブリの実行に必要なアクセス許可が含まれていますが、このアセンブリはレジストリを読み込もうとするので、レジストリの読み込みを許すアクセス許可をアクセス許可セットに追加する必要があります。
CustomPS.AddPermission(new RegistryPermission(
PermissionState.Unrestricted));
「All Code」メンバシップ条件を作成します。
AllMembershipCondition allCodeMC = new AllMembershipCondition();
新しく作成されたカスタムアクセス許可セットオブジェクトとメンバシップ条件オブジェクトを用いて、カスタムコードグループを作成します。これがAppDomainポリシーレベルのルートコードグループとなります。
CodeGroup allCodeCG = new UnionCodeGroup(allCodeMC,polState);
domainPolicy.RootCodeGroup = allCodeCG;
AppDomainセキュリティポリシーをプログラムで設定するので、4つの各セキュリティレベルで付与されるアクセス許可の積集合は、CLRによって決定されます。このようにして、動的に読み込まれるアセンブリの実効的なアクセス許可が決まります。 おわりに .NET Frameworkは、Enterprise、Machine、User、AppDomainの4つのセキュリティポリシーレベルをサポートしています。最初の3レベルの設定情報はXMLベースの設定ファイルに格納されます。これらの設定ファイルは、「caspol.exe」ツールか「mscorcfg.msc」MMCスナップインを用いて編集することができます。AppDomainポリシーレベルは、実行時に 著者紹介David Myers(David Myers)
1987年、テキサス大学エルパソ校にてコンピュータサイエンスの学士号を取得。MCSD認定資格(.NET)を持つ。IT分野の経験は15年におよび、うち9年はMicrosoft中心のソリューションに関するソフトウェアコンサルタントとして活動。主な活動分野は.Net Framework、C#、XML、Webサービス、SQL Serverなど。CompConTechでのコンサルティングを通じてMicrosoftのソリューション開発者として活動。お問い合わせはdtmyers0712@yahoo.comまで。
|