クライアントの権限借用

ユーザー アプリケーションで 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 を渡します。 プロバイダーで権限借用を実行できるようにするには、プロバイダー登録プロセスを変更する必要があります。

次の手順では、権限借用のためにプロバイダーを登録する方法について説明します。 この手順では、登録プロセスを既に理解していることを前提としています。 登録プロセスの詳細については、「プロバイダーの登録」を参照してください。

権限借用のためにプロバイダーを登録するには

  1. プロバイダーを表す __Win32Provider クラスの ImpersonationLevel プロパティを 1 に設定します。

    ImpersonationLevel プロパティは、プロバイダーが権限借用をサポートしているかどうかを示します。 ImpersonationLevel を 0 に設定すると、プロバイダーではクライアントから権限借用せず、WMI と同じユーザー コンテキストで要求されたすべての操作を実行します。 ImpersonationLevel を 1 に設定すると、プロバイダーによって権限借用呼び出しが使用され、クライアントに代わって実行チェック操作が行われます。

  2. 同じ __Win32Provider クラスの PerUserInitialization プロパティを TRUE に設定します。

注意

__Win32Provider プロパティ InitializeAsAdminFirstTRUE に設定してプロバイダーを登録した場合、プロバイダーでは初期化フェーズ中にのみ管理レベルのスレッド セキュリティ トークンが使用されます。 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 関数を使用すると、サーバーでは呼び出しを行ったクライアントから権限借用できます。 CoImpersonateClientIWbemServices の実装に呼び出すことで、プロバイダーでクライアントのスレッド トークンと一致するようにプロバイダーのスレッド トークンを設定することができ、クライアントから権限借用できるようになります。 CoImpersonateClient を呼び出さない場合、プロバイダーでは管理者レベルのセキュリティでコードが実行されるため、潜在的なセキュリティの脆弱性が発生します。 プロバイダーを一時的に管理者として機能させる必要がある場合、または手動でアクセスチェックを実行する必要がある場合は、CoRevertToSelf を呼び出します。

CoImpersonateClient とは対照的に、CoRevertToSelf はスレッド権限借用レベルを処理する COM 関数です。 この場合、CoRevertToSelf では権限借用レベルが元の偽装設定に戻されます。 一般に、プロバイダーは最初は管理者であり、呼び出し元または独自の呼び出しを表す呼び出しを行っているかどうかに応じて、CoImpersonateClientCoRevertToSelf の間で切り替わります。 エンド ユーザーにセキュリティ ホールを公開しないように、これらの呼び出しを正しく配置するのはプロバイダーの責任です。 たとえば、プロバイダーは、権限借用されたコード シーケンス内のネイティブ Windows 関数のみを呼び出す必要があります。

注意

CoImpersonateClientCoRevertToSelf の目的は、プロバイダーのセキュリティを設定することです。 権限借用が失敗したと判断した場合は、IWbemObjectSink::SetStatus を介して WMI に適切な完了コードを返す必要があります。 詳細については、「プロバイダーでのアクセス拒否メッセージの処理」を参照してください。

 

プロバイダーでのセキュリティ レベルの維持

プロバイダーでは、IWbemServices の実装で CoImpersonateClient を 1 回限り呼び出し、プロバイダーの期間中は権限借用資格情報が残っていると想定することはできません。 代わりに、実装中に CoImpersonateClient を複数回呼び出して、WMI によって資格情報が変更されないようにします。

プロバイダーの権限借用を設定するメインの問題は、再入です。 このコンテキストでの再入とは、プロバイダーから情報のため WMI が呼び出され、WMI からプロバイダーへのコールバックがあるまで待機するということです。 本質的には、実行スレッドではプロバイダー コードが残され、後日コードを再入力するだけです。 再入は COM の設計の一部であり、一般的には問題ではありません。 ただし、実行スレッドが WMI に入ると、スレッドは WMI の権限借用レベルを受け取ります。 スレッドがプロバイダーに戻ったら、CoImpersonateClient の別の呼び出しで権限借用レベルをリセットする必要があります。

プロバイダーのセキュリティ ホールから身を守るために、クライアントの権限借用中にのみ WMI に再入可能な呼び出しを行う必要があります。 つまり、WMI の呼び出しは、CoImpersonateClient を呼び出した後、CoRevertToSelf を呼び出す前に行う必要があります。 CoRevertToSelf によって権限借用が WMI で実行されているユーザー レベル (一般に 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);

WMI プロバイダーの開発

名前空間セキュリティ記述子の設定

プロバイダーのセキュリティ保護