ユーザー アプリケーションが WMI プロバイダーを介してシステム上のオブジェクトからデータを要求する場合、偽装とは、プロバイダーがプロバイダーではなくクライアントのセキュリティ レベルを表す資格情報を提示することを意味します。 偽装により、クライアントはシステム上の情報への不正アクセスを取得できなくなります。
このトピックでは、次のセクションについて説明します。
- 偽装用プロバイダーの登録
- プロバイダー内での偽装レベルの設定
- プロバイダーでのセキュリティ レベルの維持
- プロバイダーでのアクセス拒否メッセージの処理
- 部分インスタンスのレポート
- 部分列挙の報告
- アクセス拒否コードのデバッグ
- 関連トピック
WMI は通常、LocalServer セキュリティ コンテキストを使用して、高いセキュリティ レベルで管理サービスとして実行されます。 管理サービスを使用すると、WMI は特権情報にアクセスする手段を提供します。 情報のプロバイダーを呼び出すとき、WMI は、そのセキュリティ識別子 (SID) をプロバイダーに渡し、プロバイダーが同じ高いセキュリティ レベルで情報にアクセスできるようにします。
WMI アプリケーションの起動プロセス中に、Windows オペレーティング システムは WMI アプリケーションに、プロセスを開始したユーザーのセキュリティ コンテキストを提供します。 通常、ユーザーのセキュリティ コンテキストは LocalServer よりも低いセキュリティ レベルであるため、ユーザーは WMI で使用できるすべての情報にアクセスするためのアクセス許可を持っていない可能性があります。 ユーザー アプリケーションが動的な情報を要求すると、WMI はユーザーの SID を対応するプロバイダーに渡します。 適切に記述された場合、プロバイダーは、プロバイダー SID ではなく、ユーザー SID を使用して情報にアクセスしようとします。
プロバイダーがクライアント アプリケーションを正常に偽装するには、クライアント アプリケーションとプロバイダーが次の条件を満たしている必要があります。
- クライアント アプリケーションは、RPC_C_IMP_LEVEL_IMPERSONATEまたはRPC_C_IMP_LEVEL_DELEGATEの COM 接続セキュリティ レベルで WMI を呼び出す必要があります。 詳細については、「WMI セキュリティの管理」を参照してください。
- プロバイダーは、偽装プロバイダーとして WMI に登録する必要があります。 詳細については、「 偽装のためのプロバイダーの登録」を参照してください。
- プロバイダーは、特権情報にアクセスする前に、クライアント アプリケーションのセキュリティ レベルに切り替える必要があります。 詳細については、「 プロバイダー内での偽装レベルの設定」を参照してください。
- この情報へのアクセスが拒否された場合、プロバイダーはエラー状態を正しく処理する必要があります。 詳細については、「 プロバイダーでのアクセス拒否メッセージの処理」を参照してください。
なりすまし用プロバイダーの登録
WMI は、偽装プロバイダーとして登録されているプロバイダーにのみ、クライアント アプリケーションの SID を渡します。 プロバイダーが偽装を実行できるようにするには、プロバイダー登録プロセスを変更する必要があります。
次の手順では、偽装のためにプロバイダーを登録する方法について説明します。 この手順では、登録プロセスを既に理解していることを前提としています。 登録プロセスの詳細については、「 プロバイダーの登録」を参照してください。
偽装のためにプロバイダーを登録するには
プロバイダーを表す__Win32Provider クラスの ImpersonationLevel プロパティを 1 に設定します。
ImpersonationLevel プロパティは、プロバイダーが偽装をサポートしているかどうかを示します。 ImpersonationLevel を 0 に設定すると、プロバイダーはクライアントを偽装せず、WMI と同じユーザー コンテキストで要求されたすべての操作を実行します。 ImpersonationLevel を 1 に設定すると、プロバイダーは偽装呼び出しを使用して、クライアントに代わって実行された操作を確認します。
同じ__Win32Provider クラスの PerUserInitialization プロパティを TRUE に設定します。
注
__Win32Provider プロパティ InitializeAsAdminFirst を TRUE に設定してプロバイダーを登録した場合、プロバイダーは初期化フェーズ中にのみ管理レベルのスレッド セキュリティ トークンを使用します。 CoImpersonateClient の呼び出しは失敗しませんが、プロバイダーはクライアントではなく WMI のセキュリティ コンテキストを使用します。
次のコード例は、偽装のためにプロバイダーを登録する方法を示しています。
instance of __Win32Provider
{
CLSID = "{FD4F53E0-65DC-11d1-AB64-00C04FD9159E}";
ImpersonationLevel = 1;
Name = "MS_NT_EVENTLOG_PROVIDER";
PerUserInitialization = TRUE;
};
プロバイダー内での偽装レベルの設定
__Win32Provider クラス プロパティ ImpersonationLevel を 1 に設定してプロバイダーを登録した場合、WMI はプロバイダーを呼び出してさまざまなクライアントを偽装します。 これらの呼び出しを処理するには、IWbemServices インターフェイスの実装で CoImpersonateClient および CoRevertToSelf COM 関数を使用します。
CoImpersonateClient 関数を使用すると、サーバーは呼び出しを行ったクライアントを偽装できます。 CoImpersonateClient を IWbemServices の実装に呼び出すことで、プロバイダーがクライアントのスレッド トークンと一致するようにプロバイダーのスレッド トークンを設定し、クライアントを偽装できるようになります。 CoImpersonateClient を呼び出さない場合、プロバイダーは管理者レベルのセキュリティでコードを実行するため、潜在的なセキュリティの脆弱性が発生します。 プロバイダーが一時的に管理者として機能する必要がある場合、またはアクセス チェックを手動で実行する必要がある場合は、 CoRevertToSelf を呼び出します。
CoImpersonateClient とは対照的に、CoRevertToSelf は、スレッドの偽装レベルを処理する COM 関数です。 この場合、 CoRevertToSelf は偽装レベルを元の偽装設定に戻します。 一般に、プロバイダーは最初は管理者であり、呼び出し元を表す呼び出しを行うか、独自の呼び出しを行うかに応じて、 CoImpersonateClient と CoRevertToSelf の間で代替されます。 エンド ユーザーにセキュリティ ホールを公開しないように、これらの呼び出しを正しく配置するのはプロバイダーの責任です。 たとえば、プロバイダーは、偽装されたコード シーケンス内でのみネイティブ Windows 関数を呼び出す必要があります。
注
CoImpersonateClient と CoRevertToSelf の目的は、プロバイダーのセキュリティを設定することです。 偽装が失敗したと判断した場合は、 IWbemObjectSink::SetStatus を介して WMI に適切な完了コードを返す必要があります。 詳細については、「 プロバイダーでのアクセス拒否メッセージの処理」を参照してください。
プロバイダーでのセキュリティ レベルの維持
プロバイダーは、IWbemServices の実装で CoImpersonateClient を 1 回呼び出すことはできず、プロバイダーの期間中は偽装資格情報が残っていると想定しています。 代わりに、実装中に CoImpersonateClient を複数回呼び出して、WMI が資格情報を変更しないようにします。
プロバイダーの偽装を設定する主な問題は、再入です。 このコンテキストでは、再入とは、プロバイダーが WMI を呼び出して情報を取得し、WMI がプロバイダーにコールバックするまで待機する場合です。 基本的に、実行スレッドはプロバイダー コードを残し、後日コードを再入力するだけです。 再入は COM の設計の一部であり、通常は問題になりません。 ただし、実行スレッドが WMI に入ると、スレッドは WMI の偽装レベルを受け取ります。 スレッドがプロバイダーに戻ったら、 CoImpersonateClient の別の呼び出しで偽装レベルをリセットする必要があります。
プロバイダーのセキュリティ ホールから身を守るためには、クライアントの偽装を行っている間にのみ WMI に再入可能な呼び出しを行う必要があります。 つまり、WMI の呼び出しは、 CoImpersonateClient を呼び出した後、 CoRevertToSelf を呼び出す前に行う必要があります。 CoRevertToSelf により、偽装は通常、LocalSystemで実行されているユーザーレベルに設定されます。したがって、CoRevertToSelf を呼び出した後の WMI への再入呼び出しでは、ユーザーと呼び出されたプロバイダーが必要以上の権限を持たせる可能性があります。
注
システム関数または別のインターフェイス メソッドを呼び出した場合、呼び出しコンテキストが維持されるとは限りません。
プロバイダーでのアクセス拒否メッセージの処理
ほとんどのアクセス拒否エラー メッセージは、クライアントがアクセス権を持たないクラスまたは情報を要求したときに表示されます。 プロバイダーが WMI にアクセス拒否エラー メッセージを返し、WMI がこれをクライアントに渡した場合、クライアントは情報が存在することを推測できます。 状況によっては、セキュリティ違反になる可能性があります。 そのため、プロバイダーはメッセージをクライアントに伝達しないでください。 代わりに、プロバイダーが指定したクラスのセットを公開しないでください。 同様に、動的インスタンス プロバイダーは、基になるデータ ソースを呼び出して、アクセス拒否メッセージの処理方法を決定する必要があります。 その理念を WMI 環境にレプリケートするのは、プロバイダーの責任です。 詳細については、「 部分インスタンスのレポート」 および「 部分列挙のレポート」を参照してください。
プロバイダーがアクセス拒否メッセージを処理する方法を決定する場合は、コードを記述してデバッグする必要があります。 デバッグ中は、偽装が少ないための拒否と、コード内のエラーによる拒否を区別すると便利です。 コードで単純なテストを使用して、違いを特定できます。 詳細については、「 アクセス拒否コードのデバッグ」を参照してください。
部分インスタンスのレポート
一般的に、アクセス拒否メッセージの 1 つは、WMI がインスタンスを埋めるためにすべての情報を提供できない場合です。 たとえば、クライアントにはハード ディスク ドライブ オブジェクトを表示する権限がありますが、ハード ディスク ドライブ自体で使用可能な領域の量を確認する権限がない場合があります。 プロバイダーは、アクセス違反が原因でインスタンスにプロパティを完全に入力できない状況を処理する方法を決定する必要があります。
WMI では、インスタンスへの部分的なアクセス権を持つクライアントに対する単一の応答は必要ありません。 代わりに、WMI バージョン 1.x では、プロバイダーに次のいずれかのオプションが許可されます。
WBEM_E_ACCESS_DENIEDで操作全体を失敗させ、インスタンスを返しません。
WBEM_E_ACCESS_DENIED と一緒にエラー オブジェクトを返して、拒否の理由を説明します。
使用可能なすべてのプロパティを返し、使用できないプロパティに NULL を入力します。
注
WBEM_E_ACCESS_DENIEDを返しても、企業にセキュリティ ホールが作成されないことを確認します。
部分列挙の報告
アクセス違反のもう 1 つの一般的な発生は、WMI が列挙体のすべてを返すことができない場合です。 たとえば、クライアントは、すべてのローカル ネットワーク コンピューター オブジェクトを表示するアクセス権を持っていても、ドメイン外のコンピューター オブジェクトを表示できない場合があります。 プロバイダーは、アクセス違反のために列挙を完了できない状況を処理する方法を決定する必要があります。
インスタンス プロバイダーと同様に、WMI では部分列挙に対する単一の応答は必要ありません。 代わりに、WMI バージョン 1.x では、プロバイダーに次のいずれかのオプションを使用できます。
プロバイダーがアクセスできるすべてのインスタンスに対してWBEM_S_NO_ERRORを返します。
このオプションを使用する場合、一部のインスタンスが使用できなかったことをユーザーは認識しません。 行レベルのセキュリティを持つ構造化クエリ言語 (SQL) を使用するプロバイダーなど、多くのプロバイダーは、結果セットを定義するために呼び出し元のセキュリティ レベルを使用して、正常な部分的な結果を返します。
WBEM_E_ACCESS_DENIEDで操作全体を失敗させ、インスタンスを返しません。
プロバイダーには、必要に応じて、クライアントに状況を説明するエラー オブジェクトを含めることができます。 プロバイダーによっては、データ ソースに順次アクセスする場合があり、列挙の途中まで拒否が発生しない場合があることに注意してください。
アクセスできるすべてのインスタンスを返しますが、エラー以外の状態コード WBEM_S_ACCESS_DENIEDも返します。
プロバイダーは、列挙中に拒否を書き留める必要があり、インスタンスの提供を続け、エラー以外の状態コードで終了する可能性があります。 プロバイダーは、最初の拒否時に列挙を終了することもできます。 このオプションの正当な理由は、プロバイダーによって取得パラダイムが異なっていることです。 プロバイダーは、アクセス違反を検出する前に既にインスタンスを配信している可能性があります。 プロバイダーによっては、他のインスタンスの提供を継続することを選択する場合もあれば、終了を希望するプロバイダーもあります。
COM の構造により、エラー オブジェクトを除き、エラー中にはいかなる情報も逆マーシャリングすることはできません。 そのため、情報とエラー コードの両方を返すことはできません。 情報を返す場合は、代わりに非エラー状態コードを使用する必要があります。
アクセス拒否コードのデバッグ
一部のアプリケーションでは、 RPC_C_IMP_LEVEL_IMPERSONATEよりも低い権限借用レベルを使用する場合があります。 この場合、クライアント アプリケーションのプロバイダーによって行われたほとんどの偽装呼び出しは失敗します。 プロバイダーを正常に設計して実装するには、この考えを念頭に置く必要があります。
既定では、プロバイダーにアクセスできる権限借用の他のレベルは RPC_C_IMP_LEVEL_IDENTIFYのみです。 クライアント アプリケーションが RPC_C_IMP_LEVEL_IDENTIFYを使用する場合、 CoImpersonateClient はエラー コードを返しません。 代わりに、プロバイダーは識別目的でのみクライアントを偽装します。 そのため、プロバイダーによって呼び出されるほとんどの Windows メソッドは、アクセス拒否メッセージを返します。 これは、ユーザーが不適切な行為をすることは許可されないため、実際には無害です。 ただし、プロバイダーの開発中に、クライアントが本当に偽装されたかどうかを知ると便利な場合があります。
このコードを正しくコンパイルするには、次の参照と #include ステートメントが必要です。
#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <wbemidl.h>
次のコード例は、プロバイダーがクライアント アプリケーションを正常に偽装したかどうかを確認する方法を示しています。
DWORD dwImp = 0;
HANDLE hThreadTok;
DWORD dwBytesReturned;
BOOL bRes;
// You must call this before trying to open a thread token!
CoImpersonateClient();
bRes = OpenThreadToken(
GetCurrentThread(),
TOKEN_QUERY,
TRUE,
&hThreadTok
);
if (bRes == FALSE)
{
printf("Unable to read thread token (%d)\n", GetLastError());
return 0;
}
bRes = GetTokenInformation(
hThreadTok,
TokenImpersonationLevel,
&dwImp,
sizeof(DWORD),
&dwBytesReturned
);
if (!bRes)
{
printf("Unable to read impersonation level\n");
CloseHandle(hThreadTok);
return 0;
}
switch (dwImp)
{
case SecurityAnonymous:
printf("SecurityAnonymous\n");
break;
case SecurityIdentification:
printf("SecurityIdentification\n");
break;
case SecurityImpersonation:
printf("SecurityImpersonation\n");
break;
case SecurityDelegation:
printf("SecurityDelegation\n");
break;
default:
printf("Error. Unable to determine impersonation level\n");
break;
}
CloseHandle(hThreadTok);
関連トピック