Windowsの偽装トークンの知識マップ(知識グラフ)
Windowsの偽装トークンは、プロセス全体を別ユーザーにする権限昇格ではなく、スレッド単位でアクセスチェックのセキュリティコンテキストを切り替える仕組みで、Identification・Impersonation・Delegationといった偽装レベルによって何ができるかが決まる。Win32 APIで偽装する場合は必ずRevertToSelfで戻す必要があり、.NETのRunImpersonated/RunImpersonatedAsyncは偽装トークンをExecutionContextに載せて運ぶため、偽装スコープの中からTask.Runへfire-and-forgetで処理を投げると、コード上のスコープを超えて偽装されたまま処理が動き続けるという壊れ方をする。プロセスを別ユーザーとして起動したい場合は偽装トークンではなくプライマリトークンが必要で、ネットワーク越しに利用者本人の権限を使いたいダブルホップ問題にはKerberosの委任設計が必要になる。
偽装トークン
スレッドに付き、そのスレッドが別のセキュリティコンテキストでアクセスチェックを受けるために使われるアクセストークン。権限昇格ではない。
偽装レベル
偽装トークンが持つAnonymous・Identification・Impersonation・Delegationの4段階の区分で、サーバーがクライアントの権限としてどこまで動作できるかを決める。
RevertToSelf
現在のスレッドの偽装を終了し、プロセスのセキュリティコンテキストへ戻すWin32 API。try/finallyで確実に呼ぶ必要がある。
WindowsIdentity.RunImpersonated / RunImpersonatedAsync
.NETで偽装スコープをラムダ式の範囲に閉じ込めて表現できるAPIで、内部では偽装トークンをAsyncLocalに載せて管理する。
.NETのExecutionContext
AsyncLocalの値を保持しスレッド間を伝播する.NETの実行コンテキストで、Task.Runは呼び出し時点のExecutionContextを捕まえてプールのスレッドで復元する。
偽装スコープを超えて継続する偽装
.NETのRunImpersonatedが偽装トークンをAsyncLocalに載せているため、Task.Runに投げた処理がExecutionContextの伝播によって偽装されたまま動き続け、コード上の偽装スコープより長く偽装が続く状態。
LogonUser
ユーザー名・ドメイン・パスワードといった資格情報からアクセストークンを取得するWin32 APIで、ログオン種別によって返るトークンの性質が変わる。
ダブルホップ問題
Webサーバーなどで偽装した利用者の資格情報を、さらに別のサーバー(ファイルサーバー等)へ再委任できずアクセスに失敗する問題。
Kerberosの委任
サービスがクライアントの代理として別のサービスへ接続する仕組み。無制約の委任・制約付き委任・リソースベースの制約付き委任(RBCD)がある。NTLMが提供するのはローカルでの偽装に必要な認可情報まで。
制約付き委任
リソースベースの制約付き委任
SPN
KDCが接続先のサービスアカウントを特定するために使う名前。登録されていなければ、KDCはそのサービスのサービスチケットを発行できない。
サービスプリンシパル名
ドメインコントローラー
Active Directoryのディレクトリサービスを提供し、KDCの機能やNTLMのパススルー認証の相手を担うサーバー。
SafeAccessTokenHandle
トークンというカーネルオブジェクトへのハンドルを、usingでスコープ管理して確実に閉じるための.NETの安全なハンドル型。
ハンドルリーク
参照カウントやハンドルカウントの数え違いなどにより、不要になったハンドル(ファイル・イベント・スレッド・GDIオブジェクト・アクセストークンなどカーネルオブジェクトへの参照)が解放されずに積み上がっていく状態。
プライマリトークン
プロセスのセキュリティコンテキストを表すアクセストークンで、CreateProcessAsUserなどのプロセス起動に使う。
プライマリアクセストークン
管理者権限
コンピューター全体の設定を変更できる権限。
昇格
ASP.NET CoreのWindows認証
ASP.NET CoreでWindows認証を使う構成で、認証されたHttpContext.UserはWindowsIdentityを表す一方、プロセス自体はアプリケーションプールIDなどのアカウントで動作し続ける。
CreateProcessAsUser
指定したトークンのセキュリティコンテキストで新しいプロセスを起動するWin32 APIで、渡すトークンには原則としてプライマリトークンが必要になる。
Windowsサービス
ユーザーのサインインと無関係にバックグラウンドで動くWindowsのプロセス。
サービス
偽装トークンにはAnonymous/Identification/Impersonation/Delegationのいずれかの偽装レベルが伴い、そのユーザーの権限で実際にアクセスできるかどうかを左右する
Win32 APIで偽装トークンを直接扱う場合に限り、try/finallyでRevertToSelfを呼び確実にセキュリティコンテキストを元に戻すことが安全な運用の前提となる。.NETのRunImpersonated/RunImpersonatedAsyncを使う場合はスコープを抜ける際にランタイムが内部で戻すため、呼び出し側が明示的にRevertToSelfを呼ぶ必要はない
RunImpersonated/RunImpersonatedAsyncは偽装トークンを受け取り、ラムダ式の範囲だけを偽装スコープとして表現する
RunImpersonatedは偽装トークンをAsyncLocalに載せ、その値はExecutionContextと一緒にスレッド間を流れる
Task.Runは呼び出し時点のExecutionContextを捕まえてプールのスレッドで復元するため、偽装スコープ内からTask.Runへ投げた処理は偽装されたまま動き続け、呼び出し側が完了も例外も受け取れなくなることがある
偽装トークンは、LogonUserのようなユーザー名・パスワードなどの資格情報を渡すAPIから取得できる
Impersonationレベルの偽装トークンはローカルでのアクセスチェックには使えても、さらに別のリモートサーバーへ資格情報を再委任するには不十分な場合があり、これがダブルホップ問題として現れる
ダブルホップ問題を解決するには、単なるアプリケーションコードでの偽装では足りず、制約付き委任やリソースベースの制約付き委任を含むKerberosの委任設計が必要になることが多い
リモートサーバーへの委任を機能させるには、委任先のSPNを明示的に設定するなど、SPNまわりの構成が関わる
制約付き委任やリソースベースの制約付き委任の設定はアプリケーション側ではなくドメイン側で行うため、ドメインコントローラーが提供するAD DS上のアカウント属性で構成する
try/finallyでRevertToSelfを確実に呼ぶことで、戻し忘れによりスレッドが偽装ユーザーのまま後続処理を続けてしまう状態を防ぐ
トークンハンドルをSafeAccessTokenHandleとusingで管理することで、取り扱い忘れによるハンドルリークを軽減する
スレッド偽装で得た偽装トークンをOpenThreadTokenで取得し、DuplicateTokenExでプライマリトークンを作るという流れでは、プライマリトークンの生成は元の偽装トークンを前提とする
偽装は権限昇格の手段ではなく、管理者アカウントを何にでも通る便利アカウントとして偽装する設計は推奨されない
ASP.NET CoreのWindows認証では、認証されたHttpContext.Userが利用者のWindows IDを表していても処理全体が利用者権限になるわけではないため、利用者本人の権限で特定の操作を行いたい場合に限り、明示的にRunImpersonated/RunImpersonatedAsyncでスコープを作る必要がある。単に認証・認可の判定だけに使うWindows認証アプリではRunImpersonatedは不要である
プライマリトークンは、CreateProcessAsUserのようなプロセス起動APIに渡すために使う
Windowsサービスのプロセスには、そのサービスの実行アカウントを表すプライマリトークンが付く
RunImpersonated/RunImpersonatedAsyncにはSafeAccessTokenHandleを渡し、usingでそのライフタイムを管理することが前提となる