COM/COM+ セキュリティ (その 1)

Fumiaki Yoshimatsu (吉松 史彰)
株式会社アスキーNT
IT コンサルティング事業部

July 27, 2000

さて本題

準備編では、Windows セキュリティ機構についての基本を解説しました。今回は本題の COM のセキュリティ機構に踏み込んでいきましょう。 準備編その 1 でちらっと触れたとおり、COM のセキュリティ機構はその下部の OS のセキュリティ機構とは独立しています。たとえば、Windows はアクセス制御のためにトークンと DACL を使いますが、COM はそれを使っても良いし、使わなくても良いことになっています。また、認証についても、COM は他の技術にほとんどの部分を任せています。

RPC における認証

COM は認証については、RPC の認証メカニズムに依存しています。RPC 自体は COM 専用のプロトコルではありませんので、COM は RPC セキュリティをうまく利用しながら、COM オブジェクト同士の通信をセキュアにするように、動作します。以下の図を見てください。

このように、COM は RPC の上に乗る上位プロトコルで、COM からのセキュリティ呼び出しはすべて RPC 経由で特定のセキュリティプロバイダに送られるようになっています。そして、RPC のメカニズムを使って認証が行なわれます。注意してほしいのは、RPC では認証メカニズムしか提供していない、ということです。アクセス許可、トークン管理については RPC では何もしません。

RPC では、メソッド呼び出しごとに違うパラメータで認証を行なうことができます。たとえば、メソッド A は NTLM で認証を行ない、メソッド B では Kerberos を使うことも可能です(意味があるかどうかは ? ですが)。Windows NT では、認証プロトコルとしては NTLM しか使えないといっても過言ではなかったのですが、Windows 2000 では、Kerberos もサポートされるようになっています。そのため、クライアントとサーバーで、認証プロトコルをあわせる必要があります。幸い Windows 2000 には認証プロトコルを選ぶための、擬似認証プロトコル(Snego)がサポートされていますので、パラメータを Snego に設定しておけば、RPC メカニズムが認証プロトコルを決定してくれます。

RPC 認証レベル

COM プログラミングをしたことがあれば、特に DCOM を使ったネットワーク越しの COM オブジェクト呼び出しをしたことがあれば、認証レベルというパラメータを見たことがあるでしょう。たとえば、Windows NT 4.0 から標準で付属している、DCOM 構成ツール( DcomCnfg.exe )には、RPC 認証レベルを設定する場所があります。

各レベルは、それぞれ次のような動作をします。DcomCnfg では、順序良く並んでいないので、説明しやすい順番に並べ替えます。

認証レベル 動作
既定 (下記のうち、デフォルトの値が使われる)
(なし) 認証を行なわない
呼び出し クライアントからのメソッド呼び出しのたびに認証が行なわれる
パケット クライアントから送られるすべてのパケットで認証が行なわれる
パケットの整合性 認証のタイミングはパケットと同じだが、パケットでは、ヘッダ部の整合性しか保証しないのに対して、パケットの整合性では、パケット全体の整合性を保証して、データの改ざんを防止する
パケットのプライバシ 認証のタイミングはパケットと同じだが、パケットのプライバシでは、パケット全体を暗号化して覗き見を防止する

表の下に行けば行くほど、認証とその後のやり取りの保護の度合いが高まります。下のレベルは上のレベルを含んでいます。パケットのプライバシが一番安全ですが、下に行けば行くほど処理の負荷も高くなります。また、認証とは言っても、たとえばパケットレベルでは、パケットが到着するたびに、毎回毎回パスワードを確認するわけではありません。前回説明したとおり、初期認証がおわると、クライアントとサーバーには、セッションキーが残ります。このセッションキーは、クライアントとサーバーしか知らない秘密です。クライアントがこれを使ってパケットのヘッダ部に署名を入れれば、サーバーは呼び出しを送ってきたのが「あの」クライアントであることを確認できます。

上記の設定は、自分がクライアントであるときも、サーバーであるときも適用されます。たとえば、クライアントがサーバーにメソッド呼び出しを行ない、サーバーがクライアントにコールバックするようなシステムの場合、「クライアント」と「サーバー」は処理によって入れ替わることになります。最初の「クライアント」が Windows 95 で、「サーバー」は Windows NT 4.0 だった場合を考えてみてください。Windows 95 は、自分がクライアントのときはどの認証レベルでもメソッド呼び出しができますが、自分がサーバーになるときは、認証レベルは「接続」と「なし」しかサポートしません。このとき、NT側の認証レベルの設定が「パケット」になっていたら、Windows 95 から Windows NT への呼び出しは(認証が正しく行なわれれば)成功しますが、NT から 95 へのコールバックは必ず失敗します。通常の DCOM では、デフォルトの認証レベルは「接続」なので、気づきませんが、Microsoft Transaction Server ( MTS ) のパッケージの認証レベルのデフォルトは「パケット」なので、NT 上の COM サーバーをMTS環境に移植すると、途端に動かなくなることがあるわけです。

RPC: クライアントの選択

Windows の RPC では、デフォルトでは認証はオフになっています。つまり、何もせずにリモートのメソッドを呼び出すと、セキュリティ機構は働きません。認証を行ないたい場合には、クライアントが RpcBindingSetAuthInfoEx を呼び出して、認証を明示的に開始しなければなりません。認証が行なわれると、その設定はすべて Binding Handle(RpcBindingSetAuthInfoEx の第 1 引数)にキャッシュされます。以後のメソッド呼び出しに、このハンドルを使えば、セキュリティ設定が維持されたままメソッド呼び出しを行なうことができます。また、RpcBindingSetAuthInfoEx は何度でも呼び出すことができます。

そのため、処理の途中で認証レベルや偽装レベルなどの設定を、変えることも可能です。クライアントが認証を要求した場合、認証が失敗すれば、メソッド呼び出しそのものも失敗します。

RPC: サーバーの決断

サーバーは、クライアントを制御することはできません。つまり、サーバーのほうから、クライアントに対して、「認証を要求してください」という指令をだすことはできません。サーバーはクライアントの設定を確認して、要求を受け付けるか、拒否するかの決断をすることができるだけです。サーバーは呼び出しに対して、RpcBindingInqAuthClientEx を使って、クライアントの、認証にかかわる要求の内容を、把握することができます。たとえば認証プロトコルが気に入らない場合や、認証レベルが低すぎる場合に、それを拒否することができます。

また、認証を行なった場合は、サーバー側で RpcImpersonateClient を呼び出して、クライアントのトークンを見ることができます。クライアントのトークンを使って、アクセス制御を実行することが可能です。

RPC では、サーバーは認証された要求を受け取ることもあれば、認証されていない呼び出しを受け取ることもあります。RPC のメカニズム自体が、クライアントからの呼び出しを拒否することはありません。認証の要求をするかどうかは、完全にクライアントのコードに依存しています。そのため、少なくとも何らかのクリティカルな処理を行なう、すべての場所で、認証レベルなどをいちいちチェックする必要があります。

COM と RPC の関係

COM では、リモートメソッド呼び出しを行なうときに、2 つの作業を行なっています。1 つは、コールスタックのマーシャリングです。これによって、アドレス空間の違う 2 つのプロセス間で、メソッド呼び出しを行なえます。マーシャリングが終わると、そのデータを送る、リモーティングが行なわれます。マーシャリングはプロキシが、そしてリモーティングはチャネルと呼ばれる COM メカニズムで行なわれます。COM のデフォルトのチャネルは、RPC を使います。したがって、COM のリモートメソッド呼び出しには、RPC が使われます。

純粋 RPC クライアントとは違い、COM-RPC ではセキュリティがデフォルトでオンになっています。クライアントのコードに、セキュリティ関連のコードがない場合は、システムがデフォルトのセキュリティ設定を行なった上で、リモート呼び出しを行ないます。認証レベル、認証プロトコル、偽装レベルなどのデフォルト値は、レジストリに設定することができます。何も設定されていない場合は以下のようになります。

設定項目 デフォルト
認証レベル 接続
偽装レベル 識別する
セキュリティサポート

プロバイダ(認証プロトコル)
Windows 2000 Snego(KerberosaNTLMSSP)
Windows NT 4.0 NTLMSSP

COM では、セキュリティをオンにしたり、デフォルト設定を行なう作業はするものの、認証自体は RPC にお任せしています。

プロキシの仕事

COM では、セキュリティ関連の設定はプロキシマネージャが行ないます。したがって、プロキシ-スタブのペアを使って呼び出しが行なわれる限り、かならずセキュリティ設定をした呼び出しが使われることになります。デベロッパーは、RPC と同じように、セキュリティ設定を途中で変更することもできます。このために、COM セキュリティ機構には「ブランケット(毛布)」と呼ばれる概念があります。

セキュリティブランケットとは、子供が両親に怒られそうになったときに、自分を守るために慌ててもぐりこむ毛布と同じ意味があります。クライアントもサーバーも、理不尽な相手から自分を守るために、「毛布」をかぶるわけです。プロキシが、ブランケットの実装を提供しますので、デベロッパーは、自分のコードからブランケットの設定を確認したり、変更したりするための指令をプロキシマネージャに送ります。

COM: クライアントの選択

COM でも、RPC と同様に、クライアントが認証設定を行ないます。クライアントは、プロキシマネージャが実装している、IClientSecurity インターフェイスを要求し、IClientSecurity::QueryBlanket メソッドで自分のブランケットの現在の設定を確認することができます。変更が必要な場合は、IClientSecurity::SetBlanket で、変更することができます。この呼び出しは RPCのRpcBindingSetAuthInfoEx にマッピングされることになります。クライアントが認証を要求し( COM のリモートメソッド呼び出しでは、放っておけば必ず要求されます)、認証が失敗した場合、呼び出しも失敗します。

COM: サーバーの決断

COM でも、サーバーがクライアントを制御できない点は、RPC と変わりません。COM の場合、サーバーはスタブマネージャが実装している、IServerSecurity インターフェイスを要求し、IServerSecurity::QueryBlanket メソッドで、クライアントのブランケットの内容を確認することができます。ここでもやはり、認証レベルが低すぎるなどの理由がある場合、サーバーが呼び出しを拒否することができます。認証がきちんと行なわれた場合は、IServerSecurity::ImpersonateClient メソッドで、クライアントを偽装してアクセスチェックを行なうことができます。

らくちん COM セキュリティ

RPC クライアントは、明示的に認証を開始しない限り、セキュアな呼び出しはできません。RPC サーバーは、認証済みクライアント、認証なしクライアントからの無差別なアクセスに対抗するためのコードを書かなければなりません。クライアントとサーバーが、協調して認証プロトコルや認証レベルを合わせないと、セキュリティは無残に崩壊します。そのため、クライアントとサーバーで、認証についての設定を交渉するコードを、書かなければならないときがあります。しかし、COM は違います。COM では、クライアントは1度だけ CoInitializeSecurity を呼び出せば事足りるようになっています(デフォルト設定で良ければ、これすら必要ありません)。サーバーも、1 度だけ CoInitializeSecurity を呼び出して、自分が受け付ける、最低限の認証パラメータを定義することができます。あとはシステムが、クライアントの要求と、サーバーの許容範囲を吟味して、最適な設定をしてくれるようになっています。ここで決められた設定が、この後クライアントで作成されるプロキシのデフォルトの設定になります(上述のとおり、IClientSecurity インターフェイスで変更できます)。クライアントの要求がサーバーの許容範囲よりも低い場合、呼び出しはサーバーに到達する前に拒否されます。このため、COM サーバーでは、呼び出しごとにいちいち認証の確認をする必要がありません。

CoInitializeSecurity

HRESULT CoInitializeSecurity(
  PSECURITY_DESCRIPTOR pVoid,
  LONG cAuthSvc,
  SOLE_AUTHENTICATION_SERVICE * asAuthSvc,
  void * pReserved1,
  DWORD dwAuthnLevel, 
  DWORD dwImpLevel,
  SOLE_AUTHENTICATION_LIST * pAuthList,
  DWORD dwCapabilities,
  void * pReserved3
);

CoInitializeSecurity は、クライアント、サーバーともに、プロセス内で 1 度だけ呼ぶことができる、COM ライブラリ関数です。引数がたくさんありますが、クライアントが呼ぶ場合と、サーバーが呼ぶ場合とで、意味が変わるものがあります。

第 1 引数: pVoid

これはアクセス制御のために使われる引数で、クライアントでは設定しません。サーバーでは、NULL、APPID へのポインタ、IAccessControl へのポインタ、Win32 セキュリティディスクリプタのいずれかを指定します。内容は違うものの、機能はすべて、アクセスしてきたクライアントが、サーバーのオブジェクトにアクセスできるかどうかを判断するために使われます。

第 2 引数: cAuthSvc、第 3 引数: asAuthSvc

どの認証サービスを利用するのかを指定します。CAuthSvc に -1 を指定すれば、COM に選ばせることができます。

第 5 引数: dwAuthnLevel

認証レベルを指定します。レベルは、DcomCnfg に設定できるものと同じです。クライアント側では、クライアントがプロキシのデフォルト値にしたいと思っているレベルを指定します。サーバー側では、受け入れられる最低レベルを指定します。2 者がずれている場合は、システムが調整を行ないます。調整された結果は、常にクライアントとサーバーのどちらか高いほうのレベルが使われます。これによって、サーバーが指定した最低レベルが、守られるようになります。もしもクライアントが IClientSecurity::SetBlanket で認証レベルを下げたとしても、サーバーが指定した最低レベルに達していなければ、チャネルが自動的に呼び出しを拒否してくれるので、サーバー側でその状況を考慮したコードを書く必要はありません。

第 6 引数: dwImpLevel

偽装レベルを指定します。この引数はクライアントからしか指定できません。クライアントは、偽装レベルを指定して、サーバーが自分を偽装したときに、何ができるのかを制御することができます。偽装レベルも DcomCnfg で設定できる内容と同じです。

偽装レベル 動作
匿名 偽装不可
識別する 偽装して、アクセスチェックを行なうことが可能。実際にリソースにアクセスすることはできない
偽装する 偽装して、アクセスチェック、ネットワーク上の1ホップ以内(ローカルも含む)のリソースにアクセスできる
委任する 偽装して、ネットワーク上のあらゆるリソースにアクセスできる

匿名レベルは、現在まで実装されていません。また、前回触れたとおり、委任レベルをサポートするには、Kerberos が必要です。したがって、Windows NT では不可能です。

第 7 引数: pAuthList

クライアントでのみ設定します。また、Windows NT 4.0 では NULL にしなければなりません。Windows 2000 では、認証サービスごとの設定を行なうことができます。

第8引数: dwCapabilities

主に、第 1 引数: pVoid の意味を指定します。

クライアントとサーバーで CoInitializeSecurity をそれぞれ1度だけ呼び出せば、あとはいちいちコードを書かなくても、プロキシやチャネルが、プロセス全体の、セキュリティの最低限のレベルを維持してくれるようになっています。クライアントが、プロセス内で扱っている特定のインターフェイスのセキュリティ設定を変更したい場合は、IClientSecurity を使うことができます。

アクセス制御

COM は、認証は RPC に任せています。アクセス制御はどうでしょうか。アクセス制御は、CoInitializeSecurity の第1引数でおこなうことができます。ここに何を指定したかによって、方法は変わってきます。ですがその前に、リモートメソッド呼び出しでは、肝心の COM サーバーが起動していない場合が考えられます。そこで、リモート呼び出しにあわせて、プロセスを立ち上げなければならないわけですが、これも誰がやっても良い、というものではありません。そこで、COM では、サーバーの起動制御をレジストリで行なえるようになっています。DcomCnfg で設定できる「起動アクセス許可」がそれです。

このチェックのときは、まだCOMサーバープロセスが起動していないわけですから、この設定はコードで行なうことはできません。したがって、起動許可を設定できるのは、ここだけ、ということになります。サーバーが起動したら、その後のオブジェクトへのアクセス制御は、DcomCnfg の「アクセス許可」設定、または CoInitializeSecurity の第1引数が使われます。CoInitializeSecurity が常に優先されます。

私は誰?

認証とアクセス制御の大雑把な仕組みがわかったところで、1 つ疑問が湧いてきませんか?認証プロトコルの仕組みはこれでいいとしても、認証してもらう「ユーザー」とはいったい誰なのでしょうか。これまでは単純に「クライアント」や「ユーザー」と呼んでいましたが、果たしてその実体は…?

COM では、3 種類の方法で、クライアントがオブジェクトにアクセスすることができます。クラスファクトリオブジェクトを取得して、オブジェクトを作成してもらう方法( CoGetClassObject )、CLSID を指定してクラスの新インスタンスを作成する方法( CoCreateInstanceEx )、そして、何らかの形でシリアライズされた情報からオブジェクトを取得する方法( CoGetInstanceFrom** )です。どの API も、引数として COSERVERINFO 構造体を取り、その中でクライアントのアイデンティティを指定することができます。ここで指定したアイデンティティが、「ユーザー」の実体その 1 です。

この「ユーザー」が認証され、新しくサーバーが起動されて、オブジェクトが作成されようとするときに、上記の起動アクセス許可の DACL と、認証されたユーザーのトークンが比較され、起動できるかどうかが決定されます。ここで指定する「ユーザー」は、この起動アクセス許可の制御にのみ、影響します。オブジェクトへのアクセスのときは、別のアイデンティティがアクセス制御に使われます。

オブジェクトへのアクセスのときに使われるのは、上述のプロキシのセキュリティブランケットで設定されているアイデンティティです。これは、CoInitializeSecurity 呼び出しで設定された値、または IClientSecurity::SetBlanket で明示的に指定した値になります。通常は( IClientSecurity::SetBlanket を明示的に呼び出して、アイデンティティ情報を変更しない限り)、プロキシのアイデンティティは、クライアントを実行しているユーザーになります。これが「ユーザー」の実態その 2 です。要するに、クライアントのプロセスを起動したユーザーが、オブジェクトにアクセスしているとみなされるわけです。わかりやすいですね…?でも、クライアントのプロセスを起動したユーザーって誰なのでしょう?以下の図のようなパターンを想像してください。

この場合、オブジェクトはクライアントと同じプロセスに存在しますので、オブジェクトにアクセスしているのが、クライアントを起動した Fumiaki というユーザーである、と考えても何も問題はありません。

この場合はどうでしょう?クライアントからのアクセス要求にしたがって、アウトプロセスサーバーが起動され、その中のオブジェクトにアクセスしようとしています。このサーバーを起動したのは誰でしょうか?これは DcomCnfg で決めることができます。

オブジェクトの DcomCnfg の設定が「起動したユーザー」になっていれば、図の場合は Fumiaki がサーバーを起動したことになります。「対話ユーザー」になっている場合、 サーバーが実行されるコンピュータに、現在ログオンしているユーザーが、サーバーを起動したことになります。もし誰もログオンしていなければ、起動は失敗します。「次のユーザー」になっている場合は、ここで指定したユーザーがサーバーを起動したことになります。オブジェクトへのアクセスはいずれの場合も( SetBlanket を呼び出さない限り)、Fumiaki ということになります。

この場合はどうでしょうか。最初のオブジェクトへのアクセスは先ほどと同じです。しかし、2 つめ(緑色)のオブジェクトへのアクセスでは、どうなるでしょうか。この場合、選択肢は 2 つあります。1 つは、Fumiaki が 2 つ目のオブジェクトのサーバーを起動する方法です。もう 1 つは、1 つ目のオブジェクトのサーバーを起動したユーザー(これは上述の 3 つの設定によって変わります)です。 Windows 2000 以前の COM では、この場合は必ず 2 つ目の方法が使われました。つまり、アクセスしに行くクライアントを起動したユーザーが、次のオブジェクトへアクセスしに行ったことになります。これによって、本来のユーザーである Fumiaki の情報は消滅してしまいます。本来のユーザーである Fumiaki が 2 つ目のオブジェクトにアクセスしたことにするには、Windows 2000 と Kerberos が必要です。これは、偽装レベルのうち「委任」レベルをサポートするために、Windows 2000 と Kerberos が必要だからです。このため、1 つ目のオブジェクト(中間のオブジェクト)の DcomCnfg の設定が「起動したユーザー」になっていると、Windows 2000 + Kerberos の環境以外では、2 つ目のオブジェクトへのアクセスはできません。必ず、「対話ユーザー」または「次のユーザー」にしておく必要があります。これが COM のアイデンティティ管理の原則です。

ここまでのまとめ

COM では、以上のような非常に簡単なセキュリティ機構を提供しています。しかし、現代のシステムでは、プロセスレベルのアクセス制御だけでは解決できない、複雑なセキュリティ設定のほうが、むしろ一般的です。たとえば、オブジェクトにはアクセス可能でも、ある特定のメソッドは、呼び出せる人を制限したいことがあります。もっと細かく、メソッドは呼び出し可能でも、その引数の値次第では、職務権限上、アクセスを拒否しなければならない場合もあります。 COM はそこまで細かいセキュリティ機構は用意していません。あとはデベロッパーに下駄が預けられています。つまり、より細かいアクセス制御を行なうために、細かさに比例する量のコードを書かなければならないのです。コードを大量に書けば、それだけバグが忍び込む確率が上がります。とりわけ、セキュリティ関連のコードでは、バグはシステムに致命的な影響を及ぼします。できるだけ簡単に、できるだけ細かくアクセス制御を行ないたい、しかし、コードはあまり書きたくない、という相反する要求が生まれてきます。

COM は Windows NT 4.0 Option Pack とともに、この問題に対する解決策を提供し始めました。そしてそれは、Windows 2000 でより高められました。お気づきですよね?そう、MTS と COM+ のことを言っています。それでは次回は MTS、COM+ のセキュリティ モデルについて説明しましょう。次回は、これまでのようにオンライン/一方通行での説明ではなく、オフライン/インタラクティブな説明をしたいと思います。どうやって?Tech・Ed 2000 Yokohama のセッション 「3-406 COM+ セキュリティ」 でこの詳細をご紹介します。

それでは Tech・Ed 2000 Yokohama でお会いしましょう。


Fumiaki Yoshimatsu:株式会社アスキー NT に勤務。システムインテグレータで開発、技術サポートを経験した後、1997年より マイクロソフト認定トレーナー(MCT) として、主に Windows DNA (2000)、COM(+)、MTS、ADO/OLE DB などのテクノロジにフォーカスした、デベロッパー向けのトレーニング コースの開発および教育に従事しています。また、それらのテクノロジをもとにしたソリューション構築のためのコンサルティング活動を行なっています。7/26-28 開催の Tech・Ed 2000 Yokohama では、「3-406 COM+ セキュリティ」のセッションスピーカーを担当します

 

 

ページのトップへ