解説
インサイド .NET Framework [改訂版]
第9回 コード・アクセス・セキュリティ(その4)
吉松 史彰
2003/09/03
|
|
アクセス許可チェックのタイミング
自分を呼び出すに至ったコールスタックは毎回変わる可能性があるので、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のチェックも、実行時に型をロードするタイミングで行われる。
Insider.NET 記事ランキング
本日
月間