解説

インサイド .NET Framework [改訂版]

第9回 コード・アクセス・セキュリティ(その4)

吉松 史彰
2003/09/03
Page1 Page2 Page3 Page4 Page5 Page6 Page7

アクセス許可チェックのタイミング

 自分を呼び出すに至ったコールスタックは毎回変わる可能性があるので、Demandメソッド(あるいは属性)によるコールスタックのチェックは、メソッドの実行時に毎回行われる。アクセス許可がチェックされるのは実行時ということだ。

 しかし、これだけでもう安心というわけにはいかない。複雑なオブジェクト指向プログラミングを行っていると、Demandのようにコードの実行時にチェックをするだけではふさぐことができないセキュリティ・ホールが存在してしまう場合がある。そのため、コード・アクセス・セキュリティでは表1のようなタイミングでアクセス許可がチェックできるようになっている。

チェックのタイミング 名前 対象
型のロード時 InheritanceDemand クラスとメソッド
JITコンパイル時 LinkDemand クラスとメソッド
実行時 Demand、Assert、Deny、PermitOnly クラスとメソッド
アセンブリのロード時 RequestMinimum、RequestOptional、RequestRefuse アセンブリ
表1 アクセス許可チェックのタイミング
「名前」は、属性によりアクセス許可を設定する場合にパラメータで指定するSecurityAction列挙体の値。

型のロード時におけるチェック(InheritanceDemand)

 InheritanceDemandは、型がメモリ上にロードされるタイミングでチェックを行うための仕組みだ。名前のとおり、継承クラスが指定されたアクセス許可を持っていることを強制して、悪意の開発者が善意のコードを継承して、悪意のあるコードを実行するのを防ぐ働きがある。

 例えば次のようなプラグイン対応のアプリケーションを考えてみよう。このアプリケーションは、構成ファイルに指定されているプラグインをロードして、それにファイルを渡して読み取ってもらうという構造になっている(図5)。

図5 プラグイン対応のアプリケーション
 
using System;
using System.Configuration;
using System.Reflection;
using System.IO;

class App {
  static void Main() {
    int i = 0;
    // 構成ファイルを読み取る
    string typename = ConfigurationSettings.AppSettings["PlugIn"];
    int sep = typename.IndexOf(',');
    string type = typename.Substring(0, sep).Trim();
    string assembly = typename.Substring(sep + 1).Trim();

    // 指定されたアセンブリをロードし、オブジェクトを作成する
    Assembly asm = Assembly.Load(assembly);
    IPlugIn pin = (IPlugIn)asm.CreateInstance(type);

    // ファイルを開いて、作成したオブジェクトに渡す
    Stream s = File.OpenRead(@"C:\boot.ini");
    string ret = pin.Read(s);
    Console.WriteLine(ret);
  }
}
プラグインをロードし、それによりファイルを読み取るアプリケーション(App.cs)
(csc.exe /t:exe /r:IPlugIn.dll App.csでコンパイル)

 実際にファイルを読み取るオブジェクトはプラグインとして後から作成されるため、アプリケーション(Appクラス)の開発者はプラグインのクラス名などが分からない。そこで、アプリケーションの開発者は、プラグインが共通に持つ基底クラスを次のように定義する。

public abstract class IPlugIn {
  public abstract string Read(System.IO.Stream ReadStream);
}
プラグインが共通に持つ基底クラス(IPlugIn.cs)
(csc.exe /t:library IPlugIn.csでコンパイル)

 上記のIPlugInクラスは、独立したアセンブリとしてプラグイン開発者に配布される。プラグインの開発者は、上記の抽象クラスを継承して、独自のクラスを開発する。

using System.IO;

namespace GoodGuy {
  public class FileReader : IPlugIn {
    public override string Read(Stream ReadStream) {
      StreamReader reader = new StreamReader(
          ReadStream, System.Text.Encoding.Default);
      string ret = reader.ReadToEnd();
      reader.Close();
      return ret;
    }
  }
}
抽象クラスIPlugInを継承したプラグイン本体(FileReader.cs)
(csc.exe /t:library /r:IPlugIn.dll FileReader.csでコンパイル)

 最後に、プラグインを入手したユーザーは次のように構成ファイルを記述して、アプリケーションがプラグインをロードできるようにする。

<configuration>
  <appSettings>
    <add key="PlugIn" value="GoodGuy.FileReader, FileReader" />
  </appSettings>
</configuration>
プラグインについての情報を記述した構成ファイル(App.exe.config)

 これでApp.exeを実行すれば、「C:\Boot.ini」ファイルの内容が参照できる。ここまではいいだろう。だが、プラグインの基底クラスは誰もが入手して独自のプラグインを開発できるので、もちろん悪意のある人間もプラグインを開発できてしまう。何らかの手口で構成ファイルやセキュリティ・ポリシーが改ざんされ、次のようなプラグインがインターネットからダウンロードされ、実行させられてしまったらどうなるだろうか。このコードでは、App.exeに読み取った内容を返す前に、その内容を自分のサイトに転送してしまう。つまり、Boot.iniファイルの内容を盗み出すわけだ。

using System.IO;

namespace BadGuy {
  public class FakeReader : IPlugIn {
    public override string Read(Stream ReadStream) {
      StreamReader reader = new StreamReader(
          ReadStream, System.Text.Encoding.Default);
      string ret = reader.ReadToEnd();
      reader.Close();

      // *****
      // 自分のサイトのWebサービスを呼び出して、
      // Boot.iniの内容を転送するコード
      // *****


      return ret;
    }
  }
}
ファイルの内容を盗み出すように修正されたプラグイン

 本連載の読者であれば、インターネットからダウンロードされたコードには大幅に低い権限しか与えられていないことを知っているだろう(.NET Framework 1.0 RTMでは、Internetアクセス許可セットを持っている。ServicePack 1/2を適用するとNothingになるが、.NET Framework 1.1ではまたInternetに戻っている)。Cドライブのルート・フォルダにアクセスする権限など、ダウンロードされたコードが持っているわけがない。にもかかわらず、このコードはきちんと実行でき、Boot.iniの内容は読み取られてしまう。StreamReaderクラスのReadメソッドは、そのストリームの元になっているリソース(ファイルやデータベースなど)へのアクセス許可はチェックしていないからだ。もともとアクセス許可を持っているApp.exeがファイルを開いているので、その時点でアクセス許可のチェックは成功する。その後はチェックされないので、C:\Boot.iniファイルに対してアクセス許可のないアセンブリ(プラグイン)であっても、そのファイルの内容を読むことができてしまうのだ。これでは明らかにマズい。

 この場合のApp.exeは、Cドライブのファイルを読んでプラグインに渡すことが分かっているので、App.exeだけでなくプラグインも、Cドライブのファイルを読むアクセス許可を持っているかをチェックしなければならない。しかし、悪意のある人間が自分のコードの中でDemandメソッドを呼び出して、みすみす例外を発生させるはずがない。App.exeの開発者には何もできないのだろうか。

 ここでInheritanceDemandの出番となる。すべてのプラグインはIPlugIn抽象クラスから継承するので、IPlugInクラスを継承するクラスはCドライブへのアクセス許可を持っていなければならないということを宣言すればよいわけだ。具体的には、次の属性をIPlugInクラスに設定するだけでよい*2

*2 IPlugInをinterfaceではなくclassとして作成した理由は、FileIOPermissionAttributeはinterfaceには設定できないからである。
 
using System.Security.Permissions;

[FileIOPermissionAttribute
    (SecurityAction.InheritanceDemand, Read="C:\\")]

public abstract class IPlugIn {
  public abstract string Read(System.IO.Stream ReadStream);
}
FileIOPermission属性を追加したIPlugIn抽象クラス

 これによって、IPlugInクラスの継承クラスが実行時にロードされそうになったときに、その継承クラスが「C:\」にアクセス許可を持っているかどうかがチェックされる。FakeReaderアセンブリがインターネットからダウンロードされたものであれば、アクセス許可は持っていないであろうから、FakeReaderクラスはロードされなくなるのだ。

 このように、基底クラスの開発者が、派生クラスが持つべきセキュリティ要件を指定できるのがInheritanceDemandの機能である。InheritanceDemandは、型がロードされるタイミングでチェックされるので、Demandメソッドのようにコードで書くことはできない。必ず属性に指定しなければならない。また、継承(Inheritance)という言葉から、このアクセス許可はコンパイル時にチェックされるような気がするかもしれないが、アクセス許可の内容は実行時に決定するものなので、InheritanceDemandのチェックも、実行時に型をロードするタイミングで行われる。


 INDEX
  解説 インサイド .NET Framework [改訂版]
  第9回 コード・アクセス・セキュリティ(その4)
    1.アクセス許可が適用されるタイミング
    2.Demandメソッドの内部動作
    3.属性によるアクセス許可チェックの指定
  4.型のロード時におけるチェック
    5.JITコンパイル時のチェックとDemandの動作の変更
    6.アクセス許可を取り戻す
    7.アセンブリ・レベルでの宣言セキュリティ
 
インデックス・ページヘ  「解説:インサイド .NET Framework [改訂版]」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間