4. セキュリティ機構

更新日: 2009 年 10 月 23 日


非常に強力で柔軟性の高いセキュリティ機構が提供されているのも .NET の大きな特徴です。

.NET 以前には、ロール ベース セキュリティと呼ばれるセキュリティ機構が提供されていました。これは、Windows が提供するセキュリティ機構で、ファイル システムやレジストリ、サービスなど、いたるところで使用されています。

例としてファイル システムを見てみましょう。ファイルのプロパティを開いて [セキュリティ] タブを開きます。ユーザー アカウントやグループが表示されます。

例としてファイルシステムを見てみましょう。ファイルのプロパティを開いて [セキュリティ] タブを開きます。ユーザー アカウントやグループが表示されます。

この画面で、ユーザーまたはグループがファイルにアクセスできるかどうかを設定できます。ここでは、user というユーザーが電卓 (calc.exe) にアクセスできないように設定しています。実際に、user アカウントを使用して電卓を実行してみると、以下のエラーメッセージが表示されます。

実際に、user アカウントを使用して電卓を実行してみると、以下のエラーメッセージが表示されます。

このように Windows では、アカウントやアカウントが所属しているグループごとに個々のファイルへのアクセスが可能かどうかが判断され、処理が実行されます。このセキュリティ機構では、たとえアプリケーションを実行する権限があったとしても、アプリケーションがアクセスするファイルにアクセス権が付与されていないと、そのファイルの読み書きなどは失敗します。この方式では、アプリケーションを実行しているユーザーを信用し、ユーザーが行えることはアプリケーションでも行えるというアクセス制御が行われます。

アプリケーションが信頼できる場合は、このようなロール ベース セキュリティでも十分なセキュリティが確保できます。しかし例えば、インターネットからダウンロードしてきたプログラムを実行する場合を考えてみましょう。信頼できるサイトからダウンロードした、信頼できるアプリケーションなら問題ありませんが、必ずしも信頼できないアプリケーションであったとしても、ユーザーが実行を指示すれば、ユーザーの権限でアプリケーションは実行されてしまいます。そのため、スパイウェアやアドウェアなどのアプリケーションがインストールされて、個人情報が送信されたり、意図しないサイトが開いたりするといった問題が発生することがあります。このような問題に対応するために、.NET では、コード アクセス セキュリティと呼ばれるより堅牢なセキュリティ機構が採用されています。

ページのトップへ


コード アクセス セキュリティ

.NET で採用されているコード アクセス セキュリティ (以下、CAS) は、ロール ベース セキュリティを置き換えるものではなく、ロール ベース セキュリティ上で動作するセキュリティ機構です。そのため、ロール ベース セキュリティで権限が与えられていないファイルにアクセスすることはできません。

そのため、ロールベース セキュリティで権限が与えられていないファイルにアクセスすることはできません。

CAS では、実行しようとしているユーザーを信頼するのではなく、これから実行しようとしているコードが信頼できるかどうかを判断します。そして信頼できる場合にのみ、そのコードが実行されます。

例えば、プログラムがファイルにアクセスしようとしたとき、それがファイルにアクセスする権限を持っているかどうかを判断します。ここでいう、「ファイルにアクセスする権限」というのは、ファイルに対するアクセス権ではなく、「ファイルにアクセスする処理」を実行する権限です。つまり、CAS では、アクセス対象に対するアクセス権ではなく、行為に対してアクセス権を設定します。

アクセス権は、アセンブリに対して付与されます。アセンブリが CLR によって読み込まれると、アセンブリが評価されてアクセス権が付与されます。アセンブリの評価とは、エビデンスと呼ばれるコードの出所を判定する行為です。エビデンスを定義した集合をコード グループと呼び、これに付与されるアクセス許可の集合をアクセス許可セットと呼びます。アセンブリのエビデンスを評価した結果、該当するコード グループが決まり、コード グループに結び付けられているアクセス許可セットがアセンブリに付与されます。アクセス許可セットというのは、アセンブリが実施できる操作の固まりと考えることができます。

アクセス許可セットというのは、アセンブリが実施できる操作の固まりと考えることができます。

アセンブリが信頼できるかどうかを判定するエビデンスは、アセンブリの出所を評価するためのものです。.NET Framework 2.0 で定義されているエビデンスは、主に以下の 7 つになります。

エビデンス 説明
アプリケーション ディレクトリ アプリケーションのあるディレクトリ
ハッシュ アセンブリのハッシュ値
発行者 アセンブリのデジタル署名
サイト アセンブリのダウンロードサイト
厳密な名前 アセンブリの厳密名
URL アセンブリのダウンロード URL
ゾーン アセンブリのダウンロード元ゾーン

例えば、microsoft.com からダウンロードしたアセンブリに対してアクセス許可を付与する場合は、サイトが「microsoft.com」のアセンブリ、または URL が「https://www.microsoft.com/*」のアセンブリというように指定できます。サイトで指定した場合は、通信プロトコルに関わらず microsoft.com を信頼することになりますが、URL で指定した場合は、プロトコルを特定した指定になります。また、URL の場合は、「file://」プロトコルを指定することによって、ローカル ファイル システムやファイル サーバーなどを指定することもできます。

次にアクセス許可について解説します。先ほどアクセス許可は、実施しようとする処理に対する権限であると説明しました。.NET では、きめ細かいセキュリティ制御を可能にするために、細かな単位でアクセス許可が定義されています。以下にその例を挙げます。

アクセス許可 許可される内容
ディレクトリ サービス Active Directory などのディレクトリ サービスを使用できる。
DNS DNS サービスを使用して名前解決できる。
イベント ログ イベント ログへの読み書きができる。
環境変数 環境変数の読み書きができる。
ファイル IO ファイルの読み書きができる。
ファイル ダイアログ 開く、保存などのファイル ダイアログを表示できる。実際にファイルの読み書きを行うためには、ファイル IO アクセス許可が必要。
分離ストレージ ファイル プログラムが分離ストレージ ファイルに情報を読み書きできる。
メッセージ キュー MSMQ などのメッセージ キューにアクセスできる。
パフォーマンス カウンタ パフォーマンス カウンタにアクセスできる。
印刷 印刷処理を実行できる。
レジストリ レジストリにアクセスできる。
リフレクション リフレクションによってアセンブリの内部にアクセスできる。
セキュリティ .NET 内部のセキュリティ機構に関する詳細な構成を行うことができる。
サービス コントローラ Windows サービスにアクセスできる。
ソケット アクセス ソケットによる接続の確立、または受け入れができる。
SQL クライアント SQL Server に接続することができる。
Web アクセス Web 上のリソースにアクセスできる。
ユーザー インターフェイス ユーザー インターフェイスやクリップ ボードを操作できる。
X509 ストア X.509 証明書のストアにアクセスできる。

このようにファイルへのアクセス可否から環境変数へのアクセス可否まで、細かく設定できます。アセンブリは、付与されたアクセス許可の範囲でしか処理を実行できません。

それでは、これらが実際にどのように設定されているかを確認してみましょう。CAS の設定は管理ツールの「Microsoft .NET Framework 2.0 構成」から行います。

左側のツリー表示において、[マイ コンピュータ] - [ランタイム セキュリティ ポリシー] 以下の設定が、実際に CAS で使われている設定になります。[エンタープライズ (Enterprise)] [コンピュータ (Machine)] [ユーザー (User)] の 3 つは、それぞれ「企業内の設定」「コンピュータ内の設定」「ユーザーごとの設定」になります。

[エンタープライズ (Enterprise)] [コンピュータ (Machine)] [ユーザー (User)] の 3 つは、それぞれ「企業内の設定」「コンピュータ内の設定」「ユーザーごとの設定」になります。

次に [コンピュータ (Machine) ] を展開してみます。[コード グループ]、[アクセス許可セット] が表示されます。これが先ほど解説した設定です。.NET Framework のインストール時点では、マイクロソフトが想定するセキュアな設定がすでに登録されています。

.NET Framework のインストール時点では、マイクロソフトが想定するセキュアな設定がすでに登録されています。

コード グループおよびアクセス許可セットにある項目の名前からも分かるように、.NET Framework インストール時点で登録されているコード グループとアクセス許可セットはゾーンに基づいて設定されています。例えば、ファイル サーバーに配置されたアセンブリは、デフォルトではイントラネット ゾーンで動作するため、LocalIntranet_Zone コード グループで設定されているエビデンスが一致し、その結果、LocalIntranet アクセス許可セットに設定されているアクセス許可が付与されます。それでは、実際にどのような設定がされているかを確認していきましょう。

[コード グループ] - [LocalIntranet_Zone] を右クリックして、[プロパティ] をクリックします。[LocalIntranet_Zone のプロパティ] ダイアログが表示されます。

コード グループ] - [LocalIntranet_Zone] を右クリックして、[プロパティ] をクリックします。[LocalIntranet_Zone のプロパティ] ダイアログが表示されます。

[メンバシップ条件] タブをアクティブにしてみましょう。ここでは実際に、「ゾーン」が「イントラネット」の場合というエビデンスの設定がされていることが分かります。

[メンバシップ条件] タブをアクティブにしてみましょう。ここでは実際に、「ゾーン」が「イントラネット」の場合というエビデンスの設定がされていることが分かります。

試しにドロップダウンを展開してみると、いくつかのエビデンスやそれに伴う設定が行えることを確認できます。設定を変更しないように注意して、次は [アクセス許可セット] タブをアクティブにしてみます。

[アクセス許可セット] に先ほどツリーで表示されていた [LocalIntranet] が選択され、下には LocalIntranet に設定されているアクセス許可が表示されます。

[アクセス許可セット] に先ほどツリーで表示されていた [LocalIntranet] が選択され、下には LocalIntranet に設定されているアクセス許可が表示されます。

他のゾーンのプロパティと比較してみると、どのようなアクセス許可が与えられているのかが分かりやすくなります。下の図は [Internet_Zone] のプロパティを表示してみたところです。

 他のゾーンのプロパティと比較してみると、どのようなアクセス許可が与えられているのかが分かりやすくなります。下の図は [Internet_Zone] のプロパティを表示してみたところです。

先ほどの一覧と比較すると、インターネット上のプログラムに与えられるアクセス許可は、イントラネット上のそれに比べて大幅に少ないことが分かります。また、たとえイントラネット内で配布されているアプリケーションやアセンブリであっても、完全に信頼するのではなく、一部のアクセス許可のみを与えてセキュアな環境を作るように設計されているのが分かります。

このように様々なアクセス許可が用意されているわけですが、実はさらにこの下の階層が用意されています。LocalIntranet のアクセス許可の 1 つである [環境変数] を選択して、[アクセス許可の表示] ボタンをクリックしてみると、[アクセス許可ビューア] ダイアログが表示されます。

LocalIntranet のアクセス許可の 1 つである [環境変数] を選択して、[アクセス許可の表示] ボタンをクリックしてみると、[アクセス許可ビューア] ダイアログが表示されます。

これを見ると、イントラネット ゾーンのアセンブリからは、環境変数の中でも USERNAME しか取得できないことが分かります。つまり、デフォルトの設定では、temp フォルダーの設定や PATH の設定などは、イントラネット ゾーンのアセンブリからは参照できません。

もちろん、イントラネットに配置するアプリケーションの場合、これだけのアクセス許可ではできることが大幅に限られてしまうため、実際の業務アプリケーションには適当でない場合もあります。そのため、信頼できるアプリケーションやアセンブリに対しては、独自にコード グループやアクセス許可セットが設定できるようになっています。

アクセス許可が与えられているかどうかは、それぞれの処理が実行されたときに評価されます。例えば、ファイル IO のアクセス許可が与えられていないファイル サーバー上のアプリケーションを実行したとき、アプリケーションの起動や、アクセス許可が与えられている処理は可能ですが、ファイル アクセスが実行されたときには次のような例外が発生することになります。

ハンドルされていない例外: System.Security.SecurityException: 型 'System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' のアクセス許可の要求に失敗しました。
   場所 System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
   場所 System.Security.CodeAccessPermission.Demand()
   場所 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs,String msgPath, Boolean bFromProxy)
   場所 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   場所 System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
   場所 System.IO.StreamReader..ctor(String path, Encoding encoding)
   場所 System.IO.File.ReadAllText(String path, Encoding encoding)
   場所 PermissionTest.Main()

この例外の情報を見てみると、File.ReadAllText メソッドを呼び出したときに、内部では CodeAccessPermission.Demand メソッドが呼ばれているのが分かりますが、このときにアクセス許可のチェックが行われます。

ところで、アクセス許可がチェックされるのはアセンブリ単位であると説明しましたが、アプリケーションが複数のアセンブリで構成されている場合はどうなるのでしょうか。セキュリティとしては当然のことながら、呼び出し先のアセンブリは、呼び出し元のアクセス許可を超えた処理を実施できません。

処理が複数のアセンブリを経由している場合には、呼び出し元までさかのぼってアクセス許可のチェックが行われます。例えば、Test.exe から呼び出された Library1.dll でファイル IO が発生した場合、呼び出されたメソッドをさかのぼってアクセス許可がチェックされ、すべてにアクセス許可があった場合にのみ、処理が実行されます。

例えば、Test.exe から呼び出された Library1.dll でファイル IO が発生した場合、呼び出されたメソッドをさかのぼってアクセス許可がチェックされ、すべてにアクセス許可があった場合にのみ、処理が実行されます。

ページのトップへ


ロール ベース セキュリティ

先ほど、コード アクセス セキュリティは、ロール ベース セキュリティの上で実行されていると説明しましたが、アプリケーションの中でも、独自にロールをチェックすることができます。

つまり、Windows 上でロール ベース セキュリティによりユーザーがプログラムを実行できるかどうかを判定し、CLR 上でプログラムが処理を実行できるかどうかを判定し、さらにプログラムでユーザーが処理を実行可能かどうかを判定できます。

ロールというのは、役割という意味ですが、ユーザーが処理を実行する役割であるかどうかを論理的に判定するセキュリティ機構がロール ベース セキュリティといえます。

プログラムでロール ベース セキュリティを利用するためには、基本的に 2 つのインターフェイスを実装する必要があります。System.Security.Principals 名前空間で定義されている IIdentity インターフェイスと IPrincipal インターフェイスです。.NET Framework では、これらのインターフェイスの実装として、Windows 認証との統合を実現する WindowsIdentity / WindowsPrincipal クラス、汎用的なロール管理を実現する GenericIdentity / GenericPrincipal クラスの 2 種類が提供されています。

例えば、Windows で管理されている論理的なロールであるグループと関連付けた例を見てみましょう。次の例では、実行中のユーザーが HR グループに所属しているかどうかを判定しています。

AppDomain.CurrentDomain.SetPrincipalPolicy(
  System.Security.Principal.PrincipalPolicy.WindowsPrincipal);

System.Security.Principal.IPrincipal principal = System.Threading.Thread.CurrentPrincipal;

if (principal.IsInRole("HR")) { // HR グループ用の処理を行う }

このようにロール ベース セキュリティを実装することで、実行中のユーザーが保持しているロールを判定して、ロールに応じた処理を行うことができます。

汎用的なロール管理の実装である GenericPrincipal / GenericIdentity クラスの記述例は以下のようになります。

GenericIdentity identity = new GenericIdentity(userId);
string[] roles = new string[] { "HR", "Employee" };

GenericPrincipal principal = new GenericPrincipal(identity, roles); Thread.CurrentPrincipal = principal;

まず、ユーザー Id を引数に GenericIdentity クラスをインスタンス化します。次に文字列の配列である roles にユーザーの権限を設定して、GenericIdentity オブジェクトと roles を引数に GenericPrincipal クラスをインスタンス化します。最後に、現在のスレッドの CurrentPrincipal プロパティに GenericPrincipal オブジェクトを設定しておきます。

以上のようなロールの設定下では、次のような IsInRole メソッドの呼び出しにより、現在実行中のユーザーにロールがあるかどうかを判定できます。

if (Thread.CurrentPrincipal.IsInRole(role))
{
  // HR ロール用の処理を行う
}

あるいは、メソッドに対して PrincipalPermission 属性によりセキュリティを宣言しておくことで、メソッドの呼び出し自体をロールによって制限することもできます。次のメソッドでは、HR ロールがない場合には System.Security.SecurityException 例外がスローされて、メソッドの呼び出しに失敗します。

[PrincipalPermissionAttribute(SecurityAction.Demand, Role = "HR")]
private static bool HRMethod()
{
  // HR ロール用の処理を行う
}

このようにロール ベース セキュリティの機構を使用することによって、アプリケーションに論理的な制限を設けることもできます。

ページのトップへ