ドライバー開発者向けの Windows セキュリティ モデル
Windows セキュリティ モデルは、セキュリティ設定が可能なオブジェクトに基づいています。 オペレーティング システムの各コンポーネントは、それが担当するオブジェクトのセキュリティを確保する必要があります。 そのため、デバイスとデバイス オブジェクトのセキュリティは、ドライバーが保護しなければなりません。
このトピックでは、Windows セキュリティ モデルをカーネルモード ドライバーに適用する方法について説明します。
Windows セキュリティ モデル
Windows セキュリティ モデルは、オブジェクトごとの権限を中心に、システム全体にわたる少数の特権の上に成り立っています。 セキュリティで保護できるオブジェクトには、プロセス、スレッド、イベントなどの同期オブジェクトのほか、ファイル、ディレクトリ、デバイスなどが一例として挙げられます。
オブジェクトの種類ごとに、一般的な読み取り、書き込み、実行の権限が、オブジェクト固有の細かな権限にマッピングされます。 たとえば、ファイルとディレクトリについて言えば、ファイルまたはディレクトリの読み取り権限と書き込み権限、拡張ファイル属性の読み取り権限と書き込み権限、ディレクトリを走査する権限、オブジェクトのセキュリティ記述子を書き込む権限などが考えられます。
セキュリティ モデルには、次の概念が含まれます。
- セキュリティ識別子 (SID)
- アクセス トークン
- セキュリティ記述子
- アクセス制御リスト (ACL)
- 特権
セキュリティ識別子 (SID)
ユーザー、グループ、ログオン セッションは、セキュリティ識別子 (SID、プリンシパルとも呼ばれます) によって識別されます。 各ユーザーは、ログオン時にオペレーティング システムによって取得される一意の SID を持ちます。
SID は、オペレーティング システムやドメイン サーバーなどのオーソリティによって発行されます。 一部の SID は既知であり、名前と識別子を持ちます。 たとえば、SID S-1-1-0 は Everyone (または World) を識別します。
アクセス トークン
すべてのプロセスはアクセス トークンを持ちます。 アクセス トークンには、プロセスの完全なセキュリティ コンテキストが記述されます。 これには、ユーザーの SID、ユーザーが属するグループの SID、ログオン セッションの SID、ユーザーに付与されたシステム全体にわたる特権の一覧が含まれます。
既定では、プロセスのスレッドがセキュリティ設定が可能なオブジェクトと対話するときは常に、プロセスのプライマリ アクセス トークンが使用されます。 ただし、スレッドはクライアント アカウントを偽装できます。 偽装したスレッドには、それ自体のプライマリ トークンに加えて偽装トークンが割り当てられます。 偽装トークンは、スレッドが偽装しているユーザー アカウントのセキュリティ コンテキストを表します。 特に、リモート プロシージャ コール (RPC) の処理でよく偽装が使用されます。
スレッドまたはプロセスの制限付きセキュリティ コンテキストを表すアクセス トークンは、"制限されたトークン" と呼ばれます。 制限されたトークン内の SID は、セキュリティ設定が可能なオブジェクトへのアクセスを拒否する (アクセスを許可しない) ようにのみ設定できます。 さらに、システム全体にわたる特権の一部も、トークンに記述されることがあります。 制限されたトークンがプロセスで使用されている間、ユーザーの SID や ID は変わりませんが、ユーザーのアクセス権が制限されます。 制限されたトークンは、CreateRestrictedToken 関数で作成します。
セキュリティ記述子
すべての名前付き Windows オブジェクト (名前のない一部のオブジェクトも含む) はセキュリティ記述子を持ちます。 セキュリティ記述子は、オブジェクトの所有者 SID とグループ SID、そしてその ACL を表します。
オブジェクトのセキュリティ記述子は、通常、オブジェクトを作成する関数によって作成されます。 ドライバーが IoCreateDevice または IoCreateDeviceSecure ルーチンを呼び出してデバイス オブジェクトを作成すると、作成されたデバイス オブジェクトにセキュリティ記述子が適用され、オブジェクトの ACL が設定されます。 ほとんどのデバイスでは、ACL がデバイス情報 (INF) ファイルで指定されます。
詳細については、カーネル ドライバーのドキュメントの「セキュリティ記述子」を参照してください。
アクセス制御リスト
アクセス制御リスト (ACL) を使用すると、オブジェクトへのアクセスをきめ細かく制御できます。 ACL は、各オブジェクトのセキュリティ記述子の一部です。
各 ACL には、0 個以上のアクセス制御エントリ (ACE) が含まれています。 各 ACE には、ユーザー、グループ、またはコンピューターを識別する 1 つの SID と、その SID に対して拒否または許可される権限の一覧が含まれます。
デバイス オブジェクトの ACL
デバイス オブジェクトの ACL は、次の 3 つの方法のいずれかで設定できます。
- デバイスの種類に応じた既定のセキュリティ記述子で設定する。
- プログラムから RtlCreateSecurityDescriptor 関数で作成し、RtlSetDaclSecurityDescriptor 関数で設定する。
- デバイスの INF ファイルにセキュリティ記述子定義言語 (SDDL) で指定するか、または IoCreateDeviceSecure ルーチンの呼び出しで指定する。
すべてのドライバーは、INF ファイルから SDDL を使用して、デバイス オブジェクトの ACL を指定する必要があります。
SDDL は、コンポーネントが文字列形式で ACL を作成できる拡張可能な記述言語です。 SDDL は、ユーザーモードとカーネルモードの両方のコードで使用されます。 次の図は、デバイス オブジェクトの SDDL 文字列の形式を示しています。
Access 値は、許可するアクセスの種類を指定します。 SID 値には、Access 値の適用先 (ユーザーやグループなど) を特定するセキュリティ識別子を指定します。
たとえば、次の SDDL 文字列では、システム (SY) にすべてのアクセスを許可し、他のすべてのユーザー (WD) には読み取りアクセスのみを許可します。
“D:P(A;;GA;;;SY)(A;;GR;;;WD)”
ヘッダー ファイル wdmsec.h にも、デバイス オブジェクトに適した定義済みの SDDL 文字列一式が含まれています。 たとえば、ヘッダー ファイルには、SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX が次のように定義されています。
"D:P(A;;GA;;;SY)(A;;GRGWGX;;;BA)(A;;GRGWGX;;;WD)(A;;GRGWGX;;;RC)"
この文字列の最初のセグメントでは、カーネルとオペレーティング システム (SY) にデバイスの完全な制御を許可しています。 2 番目のセグメントでは、ビルトイン Administrators グループ (BA) 内のすべてのユーザーにデバイス全体へのアクセスを許可しています。ただし、ACL を変更することはできません。 3 番目のセグメントでは、デバイスの読み取りと書き込みをすべてのユーザー (WD) に許可し、4 番目のセグメントでは、信頼されていないコード (RC: Untrusted Code) に同じ権限を付与しています。 ドライバーは、定義済みの文字列をそのまま使用することも、デバイス オブジェクト固有の文字列のモデルとして使用することもできます。
スタック内のすべてのデバイス オブジェクトには同じ ACL が必要です。 スタック内の 1 つのデバイス オブジェクトの ACL を変更すると、デバイス スタック全体の ACL が変更されます。
ただし、新しいデバイス オブジェクトをスタックに追加しても、新しいデバイス オブジェクトの ACL (ACL がある場合) やスタック内の既存のデバイス オブジェクトの ACL は変更されません。 ドライバーは、新しいデバイス オブジェクトを作成し、スタックの一番上に加えるとき、1 つ下位のドライバーから DeviceObject.Characteristics フィールドをコピーして、新しいデバイス オブジェクトにスタックの ACL をコピーする必要があります。
IoCreateDeviceSecure ルーチンは、WD や SY などの定義済みの SID を使用する SDDL 文字列のサブセットをサポートします。 ユーザーモード API と INF ファイルでは、完全な SDDL 構文がサポートされています。
ACL を使用したセキュリティ チェック
オブジェクトへのアクセスをプロセスから要求されると、セキュリティ チェックは、オブジェクトの ACL を呼び出し元のアクセス トークン内の SID と比較します。
システムは、必ず上から順に ACE を比較し、最初に適合する一致で比較を止めます。 そのため、ACL を作成するときは必ず、拒否 ACE が、対応する許可 ACE よりも上に来るように配置する必要があります。 次の例で、実際の比較処理の進行を示します。
例 1: ACL とアクセス トークンの比較
例 1 は、呼び出し元プロセスのアクセス トークンに対して、ACL がどのように比較されるかを示したものです。 呼び出し元が開こうとしているファイルに、次の表に示した ACL が割り当てられているとします。
サンプル ファイルの ACL
権限 | SID | Access (アクセス) |
---|---|---|
Allow | 会計 | 書き込み、削除 |
Allow | Sales | 追加 |
拒否 | 法的情報 | 追加、書き込み、削除 |
Allow | すべてのユーザー | 読み込み |
この ACL には 4 つの ACE があり、Accounting (会計)、Sales (営業)、Legal (法務)、Everyone (全員) の各グループに個別に適用されています。
次に、要求プロセスのアクセス トークンに、次の順序で 1 人のユーザーと 3 つのグループの SID が含まれているとします。
User Jim (S-1-5-21…)
Group Accounting (S-1-5-22…)
Group Legal (S-1-5-23…)
Group Everyone (S-1-1-0)
ファイルの ACL をアクセス トークンと比較する場合、システムはまず、ファイルの ACL からユーザー Jim の ACE を検索します。 該当するものがないので、次に Accounting グループの ACE を探します。 前の表で示したように、Accounting グループの ACE がファイルの ACL の最初のエントリとして表示されるため、Jim のプロセスにはファイルの書き込みと削除の権限が付与され、そこで比較が停止します。 ACL の中で、仮に Legal グループの ACE が Accounting グループの ACE よりも上にあった場合、プロセスはファイルへの書き込み、追加、削除のアクセスを拒否されます。
例 2: ACL と制限されたトークンの比較
制限されたトークンに対して ACL を比較する方法は、制限されていないトークンの場合と同じです。 ただし、制限されたトークン内の拒否 SID は、ACL 内の拒否 ACE としか一致しません。
例 2 では、制限されたトークンに対して、ファイルの ACL がどのように比較されるかを説明します。 ファイルに適用されている ACL は、前の表に示したケースと同じであるとします。 ただし、この例では、次の SID を含む制限されたトークンがプロセスに割り当てられています。
User Jim (S-1-5-21…) Deny
Group Accounting (S-1-5-22…) Deny
Group Legal (S-1-5-23…) Deny
Group Everyone (S-1-1-0)
ファイルの ACL には Jim の SID がないため、システムは Accounting グループの SID に進みます。 ファイルの ACL には Accounting グループの ACE がありますが、この ACE はアクセスを許可するものです。そのため、アクセスを拒否する、プロセスの制限されたトークン内の SID とは一致しません。 その結果、システムは Legal グループの SID に進みます。 ファイルの ACL には、アクセスを拒否する Legal グループの ACE が含まれているため、プロセスでファイルの書き込み、追加、削除を行うことはできません。
特権
特権とは、ユーザーがローカル コンピューター上でシステム関連の操作、たとえばドライバーの読み込み、時刻の変更、システムのシャットダウンを実行するための権限です。
特権は、オブジェクトではなくシステム関連のタスクやリソースに適用されるという点、そして、オペレーティング システムではなくシステム管理者によってユーザーまたはグループに割り当てられるという点でアクセス権とは異なります。
各プロセスのアクセス トークンには、プロセスに付与された特権の一覧が含まれています。 使用する前に、特権を明示的に有効にする必要があります。 特権の詳細については、カーネル ドライバーのドキュメントの「権限」を参照してください。
Windows セキュリティ モデルのシナリオ: ファイルの作成
プロセスがファイルまたはオブジェクトへのハンドルを作成するたびに、Windows セキュリティ モデルで説明されているセキュリティ構造が使用されます。
次の図は、ユーザーモード プロセスがファイルを作成しようとしたときにトリガーされるセキュリティ関連のアクションを示しています。
ユーザーモード アプリケーションが CreateFile 関数を呼び出したときにシステムがどのように応答するかがわかります。 次の箇条書きは、図中の丸囲みの番号に対応します。
- ユーザーモード アプリケーションが、有効な Microsoft Win32 ファイル名を渡して CreateFile 関数を呼び出します。
- ユーザーモード Kernel32.dll は、その要求を Ntdll.dll に渡し、そこで Win32 名が Microsoft Windows NT ファイル名に変換されます。
- Ntdll.dll が、Windows ファイル名を使用して NtCreateFile 関数を呼び出します。 Ntoskrnl.exe 内では、I/O マネージャーが NtCreateFile を処理します。
- I/O マネージャーは、要求を再パッケージ化して、オブジェクト マネージャーの呼び出しに変換します。
- オブジェクト マネージャーはシンボリック リンクを解決し、ファイルの作成先パスに対する走査権限をユーザーが持っていることを確認します。 詳細については、「オブジェクト マネージャーのセキュリティ チェック」を参照してください。
- オブジェクト マネージャーが、要求に関連付けられている基になるオブジェクト型を所有するシステム コンポーネントを呼び出します。 ファイル作成要求の場合、このコンポーネントはデバイス オブジェクトを所有する I/O マネージャーです。
- I/O マネージャーは、デバイスに対する必要なアクセス権をユーザーが持っていることを確認するために、ユーザーのプロセスのアクセス トークンと照らしてデバイス オブジェクトのセキュリティ記述子をチェックします。 詳細については、「I/O マネージャーのセキュリティ チェック」を参照してください。
- 必要なアクセス権がユーザー プロセスにある場合、I/O マネージャーはハンドルを作成し、デバイスまたはファイル システムのドライバーに IRP_MJ_CREATE 要求を送信します。
- ドライバーは、必要に応じて追加のセキュリティ チェックを実行します。 たとえば、要求でデバイスの名前空間内のオブジェクトが指定されている場合、ドライバーは、呼び出し元が必要なアクセス権を持っていることを確認する必要があります。 詳細については、「ドライバーのセキュリティ チェック」を参照してください。
オブジェクト マネージャーのセキュリティ チェック
アクセス権をチェックする役割を担うのは、そのようなチェックを実行できる最上位レベルのコンポーネントです。 オブジェクト マネージャーが呼び出し元のアクセス権を確認できる場合は、オブジェクト マネージャーがそれを行います。 そうでない場合、オブジェクト マネージャーは、基になるオブジェクト型を担当するコンポーネントに要求を渡します。 可能であればそのコンポーネントがアクセス権を検証し、できない場合は、ドライバーなどの、より深層のコンポーネントに要求を渡します。
オブジェクト マネージャーは、イベントやミューテックス ロックなどの単純なオブジェクト型の ACL をチェックします。 名前空間を持つオブジェクトの場合、型の所有者がセキュリティ チェックを実行します。 たとえば、I/O マネージャーは、デバイス オブジェクトとファイル オブジェクトの型所有者と見なされます。 オブジェクト マネージャーは、名前の解析時にデバイス オブジェクトまたはファイル オブジェクトの名前を見つけた場合、上記のファイル作成シナリオのように、名前を I/O マネージャーに引き渡します。 I/O マネージャーは、可能な場合はアクセス権をチェックします。 その名前が、デバイス名前空間内のオブジェクトを指定する場合、I/O マネージャーはデバイス (またはファイル システム) ドライバーに名前を引き渡し、要求されたアクセスを検証する役割をそのドライバーが担います。
I/O マネージャーのセキュリティ チェック
I/O マネージャーは、ハンドルを作成する際、プロセスのアクセス トークンと照らしてオブジェクトの権限をチェックし、ユーザーに付与された権限をハンドルと共に格納します。 後で I/O 要求が到着すると、I/O マネージャーはハンドルに関連付けられている権限をチェックして、要求された I/O 操作を実行する権限がプロセスに付与されるようにします。 たとえば、プロセスが後で書き込み操作を要求した場合、I/O マネージャーはハンドルに関連付けられている権限をチェックして、呼び出し元がオブジェクトへの書き込みアクセス権を持っていることを確認します。
ハンドルを複製した場合、コピーから権限を削除することはできますが、コピーに権限を追加することはできません。
I/O マネージャーは、オブジェクトを作成すると、汎用 Win32 アクセス モードをオブジェクト固有の権限に変換します。 たとえば、ファイルとディレクトリには次の権限が適用されます。
Win32 アクセス モード | オブジェクト固有の権限 |
---|---|
GENERIC_READ | ReadData |
GENERIC_WRITE | WriteData |
GENERIC_EXECUTE | ReadAttributes |
GENERIC_ALL | すべて |
ファイルを作成するには、プロセスがターゲット パス内の親ディレクトリに対する走査権限を持っている必要があります。 たとえば、\Device\CDROM0\Directory\File.txt を作成するには、プロセスに \Device、\Device\CDROM0、\Device\CDROM0\Directory を走査する権限が必要です。 I/O マネージャーがチェックするのは、これらのディレクトリの走査権限のみです。
I/O マネージャーは、ファイル名を解析するときに走査権限をチェックします。 ファイル名がシンボリック リンクの場合、I/O マネージャーはそれを完全なパスに解決し、ルートを起点として走査権限をチェックします。 たとえば、シンボリック リンク \DosDevices\D が Windows NT デバイス名\Device\CDROM0 にマッピングされているとします。 プロセスには、\Device ディレクトリに対する走査権限が必要です。
詳細については、「オブジェクト ハンドル」と「オブジェクト セキュリティ」を参照してください。
ドライバーのセキュリティ チェック
オペレーティング システム カーネルは、すべてのドライバーを実際には、それぞれ固有の名前空間を持つファイル システムとして扱います。 そのため、呼び出し元がデバイス名前空間にオブジェクトを作成しようとすると、I/O マネージャーは、パス内の各ディレクトリに対する走査権限をプロセスが持っていることをチェックします。
WDM ドライバーの場合、FILE_DEVICE_SECURE_OPEN を指定してデバイス オブジェクトが作成されていない限り、その名前空間に対して I/O マネージャーはセキュリティ チェックを実行しません。 FILE_DEVICE_SECURE_OPEN が設定されていない場合、その名前空間のセキュリティを確保する責任はドライバーが負います。 詳細については、「デバイスの名前空間アクセスの制御」と「デバイス オブジェクトの保全」を参照してください。
WDF ドライバーの場合、FILE_DEVICE_SECURE_OPEN フラグが常に設定されるため、デバイスの名前空間内のいずれかの名前へのアクセスをアプリケーションに許可する前に、デバイスのセキュリティ記述子がチェックされます。 詳細については、「KMDF ドライバーでのデバイス アクセスの制御」を参照してください。
Windows のセキュリティ境界
異なるドライバーと通信を行うドライバーや、異なる特権レベルのユーザー モードの呼び出し元との間で通信を行うドライバーは、信頼境界を越えていると考えることができます。 信頼境界とは、コードの実行が、低い特権プロセスからより上位の特権プロセスへと横断するパスをいいます。
特権レベルの差が大きいほど、ドライバーやプロセスに攻撃 (特権エスカレーション攻撃など) を仕掛けようとする攻撃者にとって、その境界は魅力的な標的となります。
脅威モデルを作成する作業には、セキュリティ境界を調べて、予期しないパスを探すことが含まれます。 詳細については、「ドライバーの脅威モデリング」を参照してください。
信頼境界を越えるデータは信頼できないので、検証する必要があります。
この図は、3 つのカーネル ドライバーと 2 つのアプリを示しています。2 つのアプリのうち 1 つはアプリ コンテナー内にあり、もう 1 つは管理者権限で実行されるアプリです。 赤い線は信頼境界の例を示しています。
アプリ コンテナーは追加の制約を提供でき、管理者レベルでは実行されないため、パス (1) はエスカレーション攻撃のリスクが高くなります。信頼境界がアプリ コンテナー (非常に低い特権プロセス) とカーネル ドライバーの間にあるためです。
パス (2) は、アプリが管理者権限で実行され、カーネル ドライバーを直接呼び出すため、リスクが低いパスです。 管理者は既にシステムに対してかなり高い特権を持っているので、攻撃者にとって、管理者からカーネルへの攻撃面は決して魅力的な標的ではありませんが、依然として注目に値する信頼境界です。
パス (3) は、脅威モデルが作成されていない場合に見過ごされる可能性がある複数の信頼境界を越えるコード実行パスの例です。 この例では、ドライバー 1 がユーザー モード アプリから入力を受け取り、ドライバー 3 に直接渡すので、ドライバー 1 とドライバー 3 の間には信頼境界があります。
ユーザー モードからドライバーに入ってくるすべての入力は信頼できないので、検証する必要があります。 他のドライバーからの入力も、前のドライバーが単純なパススルー (たとえば、ドライバー 1 がアプリ 1 からデータを受け取り、ドライバー 1 がデータの検証を行わず、ドライバー 3 にそれを転送するなど) であったかどうかによって、信頼できない場合があります。 すべての攻撃面と信頼境界を特定し、完全な脅威モデルを作成して、それらを越えるすべてのデータを検証してください。
Windows セキュリティ モデルに関する推奨事項
- IoCreateDeviceSecure ルーチンの呼び出しで、既定の ACL を強固に設定します。
- 各デバイスの INF ファイルで ACL を指定します。 厳格な既定の ACL は、これらの ACL で必要に応じて緩和することができます。
- FILE_DEVICE_SECURE_OPEN 特性を設定して、デバイス オブジェクトのセキュリティ設定をデバイス名前空間に適用します。
- FILE_ANY_ACCESS を許可する IOCTL は、そのようなアクセスが悪用されないという確信がある場合を除き、定義しないでください。
- IoValidateDeviceIoControlAccess ルーチンを使用して、FILE_ANY_ACCESS を許可する既存の IOCTLS のセキュリティを強化します。
- 脅威モデルを作成してセキュリティ境界を調べ、予期しないパスを探します。 詳細については、「ドライバーの脅威モデリング」を参照してください。
- ドライバーのセキュリティに関するその他の推奨事項については、「ドライバーのセキュリティ チェックリスト」を参照してください。