更新履歴(4件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 14章の図と説明を直しました。「偽装スコープの中から`Task.Run`で投げると、そのスレッドには偽装トークンが付いていない」と書いていましたが、.NETでは逆です。`RunImpersonated`は偽装トークンを`AsyncLocal`に載せ、`Task.Run`は呼び出し時点の`ExecutionContext`を捕まえてプールのスレッドで復元するため、投げた処理も偽装されたまま動きます。読者が実際とは反対の前提を置きかねないので、.NETとWin32の直接呼び出しで壊れ方が正反対になることを表にし、危ないのは「偽装が外れること」ではなく「外れないまま追跡できなくなること」だと書き直しました。21.3も同じ趣旨に直しています。
- トークンの中身を項目と意味の表に整理し、SIDとDACLのフルスペルを併記しました。コード例をそのまま写してパスワードをリテラルで書かないよう警告し、資格情報の保管方法の比較表を追加しました。ダブルホップの次の一手の表、偽装の開始から`RevertToSelf`までの図と、偽装スコープの外へI/Oがはみ出す図を対比で追加しました。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589850)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「Windowsの偽装トークンを正しく扱う ── スレッド単位の権限借用と安全な戻し方」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589850 https://staging.comcomponent.com/blog/2026/06/09/002-windows-impersonation-token/
- DOI(最新版)
- 10.5281/zenodo.21589850
- DOI(この版)
- 10.5281/zenodo.21732859
1. 最初に押さえるべきこと
Windows アプリケーションや Windows サービスを作っていると、「この処理だけ、別のユーザーとして実行したい」という場面があります。
たとえば、次のような場面です。
- Windows サービスから、利用者本人の権限でファイルサーバーへアクセスしたい
- 管理用アプリケーションで、特定のユーザーに見える範囲だけを確認したい
- Named Pipe、RPC、COM、IIS、ASP.NET Core などで、呼び出し元ユーザーの権限で一部の処理を行いたい
- 既存資産の都合で、処理単位ごとに利用する Windows アカウントを切り替えたい
このときに出てくるのが、偽装、アクセストークン、偽装トークンです。
ただし、ここで最初に強調しておきたいことがあります。
Windows の偽装は、「管理者になる魔法」ではありません。アクセスチェックに使われるセキュリティコンテキストを、主にスレッド単位で切り替える仕組みです。
この違いを押さえないまま実装すると、こんな問題が起きます。
- 偽装したつもりなのに、ファイルアクセスが
Access deniedになる - ローカルのファイルは読めるのに、ネットワーク共有だけ失敗する
Task.Runやasyncの途中で、いつの間にか元のユーザーに戻っている- 偽装したままログ出力や後続処理まで動いてしまい、権限の境界があいまいになる
- プライマリトークンと偽装トークンを混同し、プロセス起動で失敗する
- 「ユーザーが Administrators に入っているのに、なぜ書き込めないのか」で詰まる
この記事では、Windows の偽装トークンを、実務で安全に扱うための考え方を整理します。
攻撃手法や権限奪取の話ではありません。 Windows アプリケーション、Windows サービス、.NET アプリケーションで、正しく権限境界を扱うための話です。
なお、この記事に登場するコードは、ビルドできるサンプル一式(ライブラリ、Windows 上で実行するデモ、引数検証とガード動作のユニットテスト)として GitHub で公開しています。
windows-impersonation-token - komurasoft-blog-samples (GitHub)
この記事の知識マップ
Windowsの偽装トークンは、プロセス全体を別ユーザーにする権限昇格ではなく、スレッド単位でアクセスチェックのセキュリティコンテキストを切り替える仕組みで、Identification・Impersonation・Delegationといった偽装レベルによって何ができるかが決まる。Win32 APIで偽装する場合は必ずRevertToSelfで戻す必要があり、.NETのRunImpersonated/RunImpersonatedAsyncは偽装トークンをExecutionContextに載せて運ぶため、偽装スコープの中からTask.Runへfire-and-forgetで処理を投げると、コード上のスコープを超えて偽装されたまま処理が動き続けるという壊れ方をする。プロセスを別ユーザーとして起動したい場合は偽装トークンではなくプライマリトークンが必要で、ネットワーク越しに利用者本人の権限を使いたいダブルホップ問題にはKerberosの委任設計が必要になる。
flowchart LR
accTitle: Windowsの偽装トークンの知識マップ
accDescr: 偽装トークンが偽装レベルを伴いRevertToSelfで安全に戻す必要があること、.NETのRunImpersonatedがExecutionContextの伝播を通じてTask.Runに投げた処理を偽装させたまま動かし続けうること、プライマリトークンやダブルホップ問題との関係を示す図
impersonation_token["偽装トークン"]
impersonation_level["偽装レベル"]
revert_to_self["RevertToSelf"]
runimpersonated_dotnet["WindowsIdentity.RunImpersonated / RunImpersonatedAsync"]
executioncontext_dotnet[".NETのExecutionContext"]
lingering_impersonation_risk["偽装スコープを超えて継続する偽装"]
logonuser_api["LogonUser"]
double_hop_problem["ダブルホップ問題"]
kerberos_delegation["Kerberosの委任"]
spn["SPN"]
domain_controller["ドメインコントローラー"]
safeaccesstokenhandle["SafeAccessTokenHandle"]
handle_leak["ハンドルリーク"]
primary_token["プライマリトークン"]
admin_rights["管理者権限"]
aspnet_core_windows_auth["ASP.NET CoreのWindows認証"]
createprocessasuser["CreateProcessAsUser"]
windows_service["Windowsサービス"]
impersonation_token -->|"前提とする"| impersonation_level
impersonation_token -.->|"前提とする"| revert_to_self
runimpersonated_dotnet -->|"利用する"| impersonation_token
runimpersonated_dotnet -->|"利用する"| executioncontext_dotnet
executioncontext_dotnet -.->|"原因になり得る"| lingering_impersonation_risk
impersonation_token -->|"利用する"| logonuser_api
impersonation_token -.->|"原因になり得る"| double_hop_problem
double_hop_problem -.->|"前提とする"| kerberos_delegation
double_hop_problem -.->|"前提とする"| spn
kerberos_delegation -->|"で構成できる"| domain_controller
revert_to_self -->|"防止する"| lingering_impersonation_risk
safeaccesstokenhandle -->|"軽減する"| handle_leak
primary_token -.->|"前提とする"| impersonation_token
impersonation_token -->|"用いるのは非推奨"| admin_rights
aspnet_core_windows_auth -.->|"前提とする"| runimpersonated_dotnet
primary_token -->|"利用する"| createprocessasuser
windows_service -->|"利用する"| primary_token
runimpersonated_dotnet -->|"前提とする"| safeaccesstokenhandle
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全18件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. アクセストークンとは何か
Windows では、ユーザーやプロセスのセキュリティコンテキストを表すために アクセストークン が使われます。
アクセストークンには、だいたいこんな情報が含まれます。以降の章で繰り返し出てくる用語なので、1行ずつ意味を添えておきます。
| 項目 | 意味 |
|---|---|
| ユーザーの SID | SID は Security Identifier の略で、ユーザーやグループを一意に表す識別子です。アクセスチェックは表示名ではなくこの値で行われます |
| 所属グループ | そのユーザーが属するグループの SID の集まりです。Administrators に入っているかどうかもここに現れます |
| 権限、Privilege | 「バックアップ操作」「シャットダウン」のように、個々のオブジェクトの ACL とは別枠で与えられる特権です。有効か無効かの状態を持ちます |
| 既定の所有者 | このトークンで新しく作ったオブジェクトの所有者になる SID です |
| 既定の DACL | DACL は Discretionary Access Control List の略で、誰に何を許可するかを並べたリストです。新規作成したオブジェクトに既定で付きます |
| 制限 SID | 制限付きトークンで使われる SID の一覧です。この一覧に対しても追加のアクセスチェックが行われるため、グループに所属していても許可されないことがあります |
| 整合性レベル | Low、Medium、High などの階層です。低い整合性レベルからは、高いレベルのオブジェクトへ書き込めません。17章で扱います |
| 昇格状態 | UAC が有効な環境で、そのトークンが昇格済みかどうかです。17章で扱います |
| 偽装レベル | 偽装トークンだけが持つ値です。識別だけできるのか、実際にアクセスできるのか、リモートへ委任できるのかが決まります。7章で扱います |
| トークン種別 | プライマリトークンか偽装トークンかの区別です。6章で扱います |
ここで、アクセスチェックに効くのは名前ではなく SID とグループと整合性レベルだという点を押さえておくと、後の章で「名前は正しいのに Access denied になる」という話が理解しやすくなります。
ファイル、レジストリ、サービス、名前付きパイプ、プロセス、スレッド、イベント、ミューテックスなど、Windows の多くのオブジェクトはセキュリティ記述子を持ちます。
あるスレッドが保護対象のオブジェクトを開こうとしたとき、Windows はトークンの情報と、対象オブジェクトの ACL を照合します。
操作するスレッド
↓
どのセキュリティコンテキストでアクセスするか
↓
トークンのユーザー、グループ、権限を見る
↓
対象オブジェクトのACLと照合する
↓
許可 / 拒否を決める
この「どのセキュリティコンテキストでアクセスするか」を理解することが、偽装トークンを理解する入口です。
3. プロセスにはプライマリトークンがある
Windows の各プロセスには、通常 プライマリアクセストークン があります。
たとえば、あるユーザーがデスクトップからアプリケーションを起動すると、そのプロセスにはそのユーザーのセキュリティコンテキストを表すプライマリトークンが付きます。
Windows サービスであれば、サービスの実行アカウントのプライマリトークンが付きます。
たとえば、こんなイメージです。
MyService.exe
Primary Token: DOMAIN\svc-app
このサービス内のスレッドが、特に偽装していなければ、ファイルやレジストリへアクセスするときにはプロセスのプライマリトークンが使われます。
つまり、既定ではこうです。
Thread A
Impersonation Token: なし
↓
アクセスチェックでは Process の Primary Token を使う
この状態で C:\Data\foo.txt を開くと、DOMAIN\svc-app に対してアクセス権があるかどうかが見られます。
4. 偽装トークンはスレッドに付く
偽装が始まると、スレッドには 偽装トークン が付きます。ここが重要なところで、偽装は基本的に「プロセス全体が別ユーザーになる」というより、そのスレッドが別のセキュリティコンテキストでアクセスチェックを受けると考える方が正確です。
MyService.exe
Primary Token: DOMAIN\svc-app
Thread A
Impersonation Token: DOMAIN\alice
Thread B
Impersonation Token: なし
このとき、Thread A がファイルを開くと、DOMAIN\alice の権限でアクセスチェックされます。
一方、Thread B は偽装していないので、DOMAIN\svc-app の権限でアクセスチェックされます。
この違いを理解していないと、こういう混乱が起きます。
// Thread A で偽装したつもり
StartImpersonation(token);
// しかし、別スレッドに処理を投げている
Task.Run(() =>
{
File.ReadAllText(path);
});
// すぐ戻してしまう
RevertToSelf();
この場合、実際にファイルを読むスレッドが、期待した偽装状態で動くとは限りません。
偽装は、スコープ、スレッド、非同期処理との関係を明確にして扱う必要があります。
5. 「偽装」は権限昇格ではない
偽装という言葉は少し強く聞こえますが、実務で大事なのは、これを「権限昇格」と混同しないことです。
偽装でできるのは、基本的にはこういうことです。
サーバープロセスの権限で処理する
↓
一部の処理だけ、クライアントユーザーの権限でアクセスチェックする
たとえば、ファイルサーバーの ACL をそのまま権限制御として使いたい場合、サーバーアプリケーションが常にサービスアカウントでファイルを読むと、利用者ごとの ACL を反映できません。
そこで、リクエスト処理の一部だけ呼び出し元ユーザーとして偽装し、ファイルアクセスを行います。
HTTP / RPC / Named Pipe リクエスト
User: DOMAIN\alice
↓
サーバーアプリケーション
Process: DOMAIN\svc-app
↓
ファイルアクセス部分だけ DOMAIN\alice として偽装
↓
ファイルサーバーのACLで許可 / 拒否される
これは、アプリケーション独自の権限判定ではなく、Windows の既存 ACL を使いたいときに役立ちます。
ただし、偽装は便利な一方、設計を誤ると権限境界が分かりにくくなります。
- どの処理を誰として実行しているのか
- どこで偽装を開始したのか
- どこで確実に戻しているのか
- どのログがどのユーザー権限で出ているのか
- 例外時にも戻るのか
- 非同期処理の最後まで偽装が有効なのか
このあたりをコードで明確にすることが重要です。
6. プライマリトークンと偽装トークンを分けて考える
Windows のトークンで特に混同しやすいのが、プライマリトークン と 偽装トークン です。
大まかには、こう考えます。
| トークン | 主な用途 | 代表例 |
|---|---|---|
| プライマリトークン | プロセスのセキュリティコンテキストを表す | プロセス起動、CreateProcessAsUser |
| 偽装トークン | スレッドが別のセキュリティコンテキストで動くために使う | ImpersonateLoggedOnUser、SetThreadToken、Named Pipe のクライアント偽装 |
特に重要なのは、プロセスを起動したい場合は、原則としてプライマリトークンが必要という点です。
偽装トークンを持っているからといって、そのまま別ユーザーのプロセス起動に使えるとは限りません。
典型的には、こんな流れになります。
クライアントを偽装する
↓
OpenThreadToken で偽装トークンを取得する
↓
DuplicateTokenEx でプライマリトークンを作る
↓
CreateProcessAsUser などに渡す
逆に、「このスレッドでファイルアクセスだけ別ユーザーとして行いたい」という場合は、プロセス起動ではなく偽装トークンを使う話になります。
この2つを混ぜると、API の引数は合っているのに Access denied や The parameter is incorrect のようなエラーで迷うことになります。
7. 偽装レベルを理解する
偽装トークンには、偽装レベル があります。
代表的には 4 段階あります。
| 偽装レベル | 意味の目安 |
|---|---|
| Anonymous | サーバーはクライアントの識別情報を得られない |
| Identification | サーバーはクライアントを識別できるが、その権限でオブジェクトへアクセスする用途には使えない |
| Impersonation | サーバーはローカルシステム上でクライアントの権限として動作できる |
| Delegation | サーバーはリモートシステムに対してもクライアントの権限を委任できる |
実務でよく詰まるのは、Identification と Impersonation の違いです。
Identification は名前のとおり、相手が誰かを知るためのレベルです。
そのユーザーの権限でファイルを開くような用途には不十分です。
そのため、こういうことが起きます。
WindowsIdentity.GetCurrent().Name は期待したユーザー名に見える
↓
でもファイルアクセスは Access denied になる
この場合、名前だけを見るのではなく、偽装レベルも確認する必要があります。
.NET であれば、WindowsIdentity.ImpersonationLevel を見ると手がかりになります。
using System.Security.Principal;
WindowsIdentity identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
Console.WriteLine(identity.ImpersonationLevel);
ネットワーク越しのアクセスでは、さらに注意が必要です。
「Web サーバーでユーザーを偽装して、そのユーザーとして別のファイルサーバーや DB サーバーへアクセスする」ような構成では、いわゆるダブルホップ問題にぶつかることがあります。
この場合、単にアプリケーションコードで偽装すれば解決するとは限りません。 Kerberos、SPN、委任、制約付き委任、サービスアカウント、接続先の認証方式などを含めて設計する必要があります。
8. 偽装の基本形
Win32 API で偽装を扱うときの概念的な形はこうです。
1. 偽装に使うトークンを取得する
2. そのトークンで現在のスレッドを偽装する
3. 必要な処理だけ実行する
4. 必ず元のセキュリティコンテキストに戻す
5. トークンハンドルを閉じる
コードの形としては、必ず try / finally にします。
if (!ImpersonateLoggedOnUser(tokenHandle))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
try
{
// ここだけ偽装ユーザーとして実行する
DoWorkAsImpersonatedUser();
}
finally
{
if (!RevertToSelf())
{
// 戻せない状態は危険なので、少なくとも処理継続してはいけない
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
時系列で見ると、こうなります。
sequenceDiagram
participant App as 呼び出し元
participant Th as 処理スレッド
participant Win as Windows
participant FS as ファイル
App->>Th: 処理を依頼する
Th->>Win: ImpersonateLoggedOnUser でトークンを付ける
Note over Th: ここから利用者 alice として<br/>アクセスチェックされる
Th->>FS: ファイルを開く
FS-->>Th: alice の権限で ACL が判定される
Th->>Win: RevertToSelf でトークンを外す
Note over Th: ここからサービスアカウント<br/>svc-app に戻る
Th-->>App: 結果を返す
偽装が効いているのは、ImpersonateLoggedOnUser から RevertToSelf までの区間だけです。この区間の外で行った I/O は、偽装ユーザーではなくプロセスのアカウントで判定されます。14章で扱う非同期処理の落とし穴は、実際の I/O がこの区間の外へはみ出してしまう問題だと考えると分かりやすくなります。
大事なのは、偽装の開始よりも、確実に戻すことです。戻し忘れると、そのスレッドで後続処理が偽装ユーザーのまま動きます。
特にスレッドプールを使うアプリケーションでは、1つの処理のつもりだったものが、別のリクエストや別の処理へ影響する可能性があります。
そのため、偽装は「始めたら戻す」ではなく、小さなスコープに閉じ込めると考えるべきです。
9. .NET では WindowsIdentity.RunImpersonated を使う
.NET では、可能であれば WindowsIdentity.RunImpersonated を使うと、偽装スコープをコード上で表現しやすくなります。
SafeAccessTokenHandle を持っている場合、次のように書けます。
using Microsoft.Win32.SafeHandles;
using System.Security.Principal;
static string ReadFileAsUser(SafeAccessTokenHandle token, string path)
{
return WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(path);
});
}
この形の良いところは、偽装している範囲がラムダ式の中に閉じていることです。
RunImpersonated(token, () =>
{
// ここだけ偽装
});
// ここから外は元のコンテキスト
ファイルアクセス、レジストリアクセス、既存ライブラリ呼び出しなど、特定の処理だけを偽装したい場合は、この形が読みやすく安全です。
非同期処理では、RunImpersonatedAsync を使います。
using Microsoft.Win32.SafeHandles;
using System.Security.Principal;
static Task WriteFileAsUserAsync(
SafeAccessTokenHandle token,
string path,
string text,
CancellationToken cancellationToken)
{
return WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
await File.WriteAllTextAsync(path, text, cancellationToken);
});
}
避けたいのは、偽装スコープの内側から fire-and-forget のタスクを投げる形です。
// 良くない例
WindowsIdentity.RunImpersonated(token, () =>
{
_ = Task.Run(() =>
{
File.WriteAllText(path, text);
});
});
このコードは、「実際にファイルを書く処理」がいつ、どの実行コンテキストで動くのか分かりにくくなります。
偽装して行いたい非同期処理は、RunImpersonatedAsync の中で await し、処理が完了してからスコープを出る形にします。
10. LogonUser でトークンを得る場合
別ユーザーの資格情報からトークンを得る代表的な API に LogonUser がありますが、これは慎重に扱うべき API です。
LogonUser にはユーザー名、ドメイン、パスワードを渡します。
つまり、アプリケーション側が資格情報を扱うことになります。
実務では、このあたりに注意します。
- パスワードをコードや設定ファイルに平文で置かない
- できれば OS の認証、サービスアカウント、委任、既存の Windows 認証を使う
- 秘密情報は適切な Secret Store や運用基盤で管理する
- トークンハンドルは必ず閉じる
- ログにユーザー名以外の秘密情報を出さない
- 偽装する範囲を最小化する
このあと最小構成の例を載せますが、そのまま写して password に文字列リテラルを書き込むことだけは避けてください。 ソースコードに書いた資格情報は、リポジトリの履歴、ビルド成果物、逆コンパイル結果のすべてに残ります。設定ファイルに平文で置くのも同じことです。21.6 で改めて触れます。
では、パスワードをどこに置くのか。選択肢を整理しておきます。
| 保管方法 | 使いどころ | 注意点 |
|---|---|---|
| そもそもパスワードを持たない | 第一候補。Windows 認証、サービスアカウント、委任で済ませる | 設計段階で決める必要がある。あとから外すのは大変 |
| 実行時に対話的に入力させる | 管理者が手元で実行する運用ツール | 無人実行やサービスでは使えない。読み取った文字列は使用後すぐ手放す |
| Windows 資格情報マネージャー | 同じアカウント、同じ PC で繰り返し使う | ユーザー単位の保管庫です。サービスアカウントで使うなら、そのアカウントで登録する必要があります |
| DPAPI | 単一の PC 内で設定ファイルの値を保護したい | ProtectedData のスコープに注意します。CurrentUser なら暗号化したユーザーだけ、LocalMachine ならその PC の他のユーザーも復号できます |
| Key Vault などのシークレット管理基盤 | 複数サーバー、CI、クラウド連携がある | 基盤側の認証が必要になる。取得したシークレットのメモリ上の扱いは別途考える |
DPAPI を使う場合の具体的な書き方は、別記事の設定ファイルの機密情報を安全に保存する方法にまとめてあります。
いずれの方法でも共通するのは、保管方法を選ぶ前に「本当にアプリケーションがパスワードを受け取る必要があるのか」を検討することです。
最小構成の例を載せます。
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
internal static class NativeMethods
{
private const int LOGON32_LOGON_INTERACTIVE = 2;
private const int LOGON32_PROVIDER_DEFAULT = 0;
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool LogonUser(
string lpszUsername,
string? lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out SafeAccessTokenHandle phToken);
public static SafeAccessTokenHandle Logon(
string userName,
string? domain,
string password)
{
bool ok = LogonUser(
userName,
domain,
password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
out SafeAccessTokenHandle token);
if (!ok)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return token;
}
}
public static string ReadFileWithExplicitCredential(
string userName,
string? domain,
string password,
string path)
{
using SafeAccessTokenHandle token = NativeMethods.Logon(userName, domain, password);
return WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(path);
});
}
この例は、あくまで API の形を示すためのものです。
実務では、「そもそもアプリケーションがパスワードを受け取る設計でよいのか」を先に検討します。
多くの場合、こうした代替案を考えた方が安全です。
| やりたいこと | 代替案 |
|---|---|
| サービス全体が特定リソースへアクセスしたい | 専用サービスアカウントに必要最小限の ACL を付ける |
| 利用者本人の権限でファイルにアクセスしたい | Windows 認証と委任設計を使う |
| 管理者権限が必要な操作をしたい | サービス側に明確な管理 API を設け、アプリ側の認可で制御する |
| 一部の処理だけ別アカウントにしたい | 偽装範囲をメソッド単位に閉じ、監査ログを残す |
11. LogonUser のログオン種別に注意する
LogonUser で得られるトークンの性質は、ログオン種別によって変わります。
特に、この違いを理解せずにコピペすると、期待どおりに動きません。
| ログオン種別 | 注意点 |
|---|---|
| Interactive | 対話ログオンに近い形。ローカル操作に使いやすいが、実行環境や権限に依存する |
| Network | ネットワークログオン向け。返るトークンがプロセス起動に直接使えない場合がある |
| NewCredentials | ローカルでは現在の資格情報に近く、リモート接続時に指定資格情報を使う用途で使われることがある |
ここで言いたいのは、特定のログオン種別を暗記することではありません。
大事なのは 2 点です。
- ログオン種別によって、ローカルアクセス、ネットワークアクセス、プロセス起動での挙動が変わる
- 返ってきたトークンが、プライマリトークンなのか、偽装トークンなのかを確認する必要がある
たとえば、LOGON32_LOGON_NETWORK で得たトークンを、そのまま CreateProcessAsUser に渡して失敗する、というのは典型的な混乱です。
プロセス起動が目的なら、プライマリトークンが必要です。
必要に応じて DuplicateTokenEx でプライマリトークンを作る設計になります。
12. RevertToSelf を軽く見ない
Win32 API で偽装を開始した場合、RevertToSelf で偽装を終了します。この戻し処理は単なる後片付けではなく、セキュリティ境界を元に戻すための重要な処理です。
悪い例です。
ImpersonateLoggedOnUser(token);
DoWork();
RevertToSelf();
一見問題なさそうですが、DoWork() で例外が発生したら、RevertToSelf() が呼ばれません。
必ず finally にします。
if (!ImpersonateLoggedOnUser(token))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
try
{
DoWork();
}
finally
{
if (!RevertToSelf())
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
RevertToSelf が失敗した場合に、そのまま処理を続けるのも危険です。元の権限に戻せていないかもしれない状態で後続処理を続けると、意図しないユーザーの権限で処理が続きます。少なくとも、その処理単位は失敗として扱い、安全側に倒します。
.NET の RunImpersonated / RunImpersonatedAsync は、この戻し忘れを避けるためのスコープ表現として有用です。
13. 偽装スコープはできるだけ小さくする
偽装で一番大事な設計原則は、必要な処理だけを偽装することです。
悪い例です。
WindowsIdentity.RunImpersonated(token, () =>
{
ValidateRequest();
LoadConfiguration();
WriteDebugLog();
ReadUserFile();
UpdateDatabase();
SendNotification();
});
このように広い範囲を偽装すると、どの操作がどの権限で行われるのか分かりにくくなります。
たとえば、ログ出力先へのアクセスが偽装ユーザーの権限で行われ、ログ書き込みに失敗するかもしれません。 DB 接続が、サービスアカウントではなく偽装ユーザーの資格情報で試みられるかもしれません。 通知処理や一時ファイル作成まで、不要な権限コンテキストの影響を受けるかもしれません。
良い例は、偽装が必要な操作だけを切り出す形です。
ValidateRequest();
LoadConfiguration();
string content = WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(userFilePath);
});
UpdateDatabase(content);
WriteAuditLog(userName, userFilePath, success: true);
この形なら、偽装が必要なのは File.ReadAllText の部分だけだと分かります。
偽装は便利ですが、広げるほど読みにくく、事故りやすくなります。
14. 非同期処理では「終わるまで偽装されているか」を見る
現代の .NET アプリケーションでは、ファイル、HTTP、DB、キュー、ストレージなど、多くの処理が非同期です。
そのため、偽装と async / await の組み合わせは注意が必要です。
基本方針はこれだけです。
偽装が必要な非同期処理は、RunImpersonatedAsync の中で await する
守らないとどうなるかを、8章の図と同じ形で並べておきます。ただし先に押さえておきたいことがあります。「別スレッドへ投げると偽装が外れる」とは限りません。どちらの仕組みで偽装したかで、壊れ方が正反対になります。
| 偽装のしかた | Task.Run に投げた処理は |
起きること |
|---|---|---|
.NET の RunImpersonated / RunImpersonatedAsync |
偽装されたまま動く | 偽装がコード上のスコープより長く延びる。投げた側は完了も例外も受け取らない |
Win32 の ImpersonateLoggedOnUser を直接呼ぶ |
プロセスのアカウントで動く | 実際の I/O が偽装区間の外へ出て Access denied になる |
.NET 側がそうなるのは、RunImpersonated が偽装トークンを AsyncLocal に載せているためです。AsyncLocal の値は ExecutionContext と一緒に流れ、Task.Run は呼び出し時点の ExecutionContext を捕まえてプールのスレッドで復元します。復元のたびに走る変更ハンドラーが、そのスレッドで ImpersonateLoggedOnUser を呼び直すため、プールのスレッドにも同じ偽装が付きます。ランタイムの実装(WindowsIdentity の s_currentImpersonatedToken と CurrentImpersonatedTokenChanged)がそうなっています。
一方、Win32 のトークンはスレッドに紐づくだけで、別のスレッドへ勝手に複製されることはありません。
.NET 側の時系列を追うと、こうなります。
sequenceDiagram
participant Th as 呼び出しスレッド
participant Pool as スレッドプールの別スレッド
participant FS as ファイル
Th->>Th: RunImpersonated で偽装を開始する
Th->>Pool: Task.Run で書き込みを投げる
Note over Pool: ExecutionContext と一緒に<br/>偽装トークンも運ばれる
Th->>Th: スコープを抜けて偽装を解除する
Note over Th: 解除されるのは<br/>このスレッドの偽装だけ
Pool->>FS: 実際の書き込みはこの後に走る
Note over Pool: 偽装ユーザーのまま。<br/>いつ終わるか呼び出し側は知らない
FS-->>Pool: 結果も例外も誰も受け取らない
8章の図では、I/O が偽装の区間の内側に収まっていました。この図では、Task.Run に投げた時点で処理が別のスレッドへ移り、実際の書き込みがコード上の区間の外へ出ています。偽装が外れるから危ないのではなく、外れないまま追跡できなくなるから危ない、というのがこの形の問題です。具体的には3つが同時に起きます。
- 偽装の範囲が読めなくなる。「ここからここまで」と書いたスコープと、実際に偽装で動く範囲が一致しません
- 失敗が握りつぶされる。投げたタスクを誰も
awaitしていないので、Access deniedになっても例外はどこにも出ません - トークンをいつ閉じてよいか決まらない。
SafeAccessTokenHandleをusingで閉じると、まだ動いている処理の足元で閉じることになります
Win32 で直接偽装している場合は逆に、投げた処理がプロセスのアカウントで動きます。テスト環境ではサービスアカウントにも権限があって成功し、本番で初めて Access denied になる、という形で表面化しがちです。
どちらであっても、RunImpersonatedAsync の中で完了まで await すれば起きません。
良い例です。
await WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
await using FileStream stream = File.OpenRead(path);
using var reader = new StreamReader(stream);
string text = await reader.ReadToEndAsync();
await ProcessTextAsync(text);
});
ただし、この形でも考えるべきことがあります。
ProcessTextAsync まで偽装ユーザーで動く必要があるのか、という点です。
ファイルを読む部分だけ偽装すればよいなら、こう分けた方が安全です。
string text = await WindowsIdentity.RunImpersonatedAsync(token, async () =>
{
return await File.ReadAllTextAsync(path);
});
await ProcessTextAsync(text);
偽装スコープの中で await できるからといって、何でも入れてよいわけではありません。
非同期処理でも、偽装範囲は最小化します。
15. ASP.NET Core と偽装
ASP.NET Core で Windows 認証を使う場合も、偽装の扱いには注意が必要です。
「Windows 認証でログインしているから、リクエスト処理全体がそのユーザーとして動く」と思い込むと危険です。
一般に、アプリケーションのプロセス自体はアプリケーションプール ID やサービスの実行アカウントで動きます。 利用者の Windows ID は認証情報として得られますが、そのまま全処理が利用者権限になるとは限りません。
利用者の権限で特定のアクションを行う必要があるなら、明示的に RunImpersonated / RunImpersonatedAsync でスコープを作ります。
コードのイメージはこうです。
app.MapGet("/download", async (HttpContext context) =>
{
if (context.User.Identity is not WindowsIdentity user)
{
return Results.Unauthorized();
}
string path = GetPathFromRequest(context);
byte[] bytes = await WindowsIdentity.RunImpersonatedAsync(
user.AccessToken,
async () => await File.ReadAllBytesAsync(path));
return Results.File(bytes, "application/octet-stream");
});
この例でも、偽装しているのはファイル読み取りの部分だけです。
レスポンス生成、ログ、アプリケーション側の認可判定まで全部を偽装スコープに入れる必要があるかは、慎重に考えるべきです。
16. Access denied の見方
偽装を使った実装で、よく出るエラーは Access denied です。
このエラーが出たときに、「偽装に失敗した」とだけ考えると遠回りになります。
確認する観点を分けます。
| 観点 | 確認内容 |
|---|---|
| 本当に偽装できているか | 偽装スコープ内で WindowsIdentity.GetCurrent().Name を確認する |
| 偽装レベルは足りているか | Identification ではなく、必要なレベルになっているか |
| 対象リソースの ACL は正しいか | 偽装ユーザーに読み取り / 書き込み権限があるか |
| ローカルかリモートか | ローカルファイルでは成功し、UNC だけ失敗していないか |
| ダブルホップではないか | Web サーバーからファイルサーバーへ利用者権限で行こうとしていないか |
| 偽装スコープを抜けていないか | 実際の I/O がスコープ外や別タスクで動いていないか |
| トークン種別は合っているか | プロセス起動に偽装トークンを渡していないか |
| UAC / 整合性レベルの影響はないか | Administrators 所属でも、非昇格トークンではないか |
特に、名前だけ確認して安心しないことが大事です。
WindowsIdentity identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
このログは有用ですが、十分ではありません。
少なくともこのあたりも見ます。
Console.WriteLine(identity.ImpersonationLevel);
Console.WriteLine(identity.IsAuthenticated);
また、対象がネットワーク共有なら、アプリケーションコードだけでなく、認証方式、委任設定、SPN、サービスアカウント、ファイルサーバー側の ACL も確認します。
17. UAC と「管理者なのに失敗する」問題
Windows では、ユーザーが Administrators グループに所属していることと、現在のトークンが昇格済みであることは同じではありません。
UAC が有効な環境では、管理者ユーザーでも通常は制限されたトークンでプロセスが動き、管理者権限が必要な操作では昇格が必要になります。
そのため、こんなことが起きます。
ユーザーは Administrators に所属している
↓
でも現在のトークンは非昇格
↓
Program Files や HKLM への書き込みで Access denied
偽装でも同じです。
「偽装先ユーザーが管理者だから書けるはず」と考えるのではなく、実際に渡されているトークンがどの状態なのかを確認する必要があります。
デバッグでは、こうした観点を見ます。
- 所属グループ
- Privilege の有効 / 無効
- 整合性レベル
- 昇格状態
- 制限トークンかどうか
- リンクされた昇格トークンがあるか
Win32 API では GetTokenInformation を使って、TokenType、TokenImpersonationLevel、TokenElevationType、TokenIntegrityLevel などを確認できます。
ただし、実務上の設計としては、「管理者ユーザーを偽装して何でもする」よりも、専用サービスや管理 API に必要最小限の操作を閉じ込める方が安全です。
18. ネットワーク共有とダブルホップ
偽装で非常に多い相談が、ネットワーク共有へのアクセスです。
クライアントPC
↓ Windows認証
Webサーバー / APIサーバー
↓ 偽装してアクセスしたい
ファイルサーバー
この構成では、「Web サーバー上ではユーザー名が取れているのに、ファイルサーバーへ行くと失敗する」ことがあります。
これは、利用者の資格情報を別のサーバーへ再委任できるかという問題です。
ローカルサーバー上での偽装と、別サーバーへの委任は同じではありません。
Impersonation レベルではローカル操作には使えても、リモートサーバーに対してクライアントとして振る舞うには足りない場合があります。
ネットワーク越しに利用者本人の権限を使いたいなら、Kerberos 委任、制約付き委任、SPN、サービスアカウント、認証方式の設計が必要です。
ここで行き止まりになりやすいので、次の一手の選択肢を並べておきます。
| 方向性 | 何をするか | 向くケース | 注意点 |
|---|---|---|---|
| Kerberos 制約付き委任 | 中継サーバーのアカウントに「このサービスにだけ委任してよい」という設定を入れる | 中継サーバーとファイルサーバーが同じドメインにあり、ドメイン管理者の協力を得られる | 設定はアプリ側ではなくドメイン側です。委任先の SPN を明示的に列挙します |
| リソースベースの制約付き委任 | 委任を許可する設定を、委任される側、つまりファイルサーバー側のアカウントに持たせる | ドメインをまたぐ場合や、資源側の管理者が主導したい場合 | Windows Server 2012 以降の仕組みです。設定する場所が従来の制約付き委任と逆になります |
| 資格情報を明示的に渡す | 利用者本人ではなく、用途を限定した専用アカウントの資格情報で接続する | 委任を設定できない、または「利用者本人であること」が業務要件ではない | 資格情報の保管が必要になります。10章の表を参照してください |
| 中継しない設計にする | ファイルサーバーへのアクセスをサービスアカウントに寄せ、認可はアプリケーション側で行う | 業務ルールがアプリケーション側にある | ACL が最終判断ではなくなります。監査ログの設計が重要になります |
| クライアントから直接行かせる | サーバーを経由せず、クライアント PC からファイルサーバーへ直接アクセスさせる | 画面から共有フォルダーを開けば足りる場合 | サーバー側での一元的な制御やログ収集はできなくなります |
判断の順番としては、まず「本当に利用者本人の Windows 権限でファイルサーバーへ行く必要があるか」を決めます。必要があるなら委任の設計に入り、必要がないなら中継しない設計に倒す、という分岐です。委任の設定はアプリケーション開発者だけでは完結しないため、必要だと判断した時点で早めにドメイン管理者へ相談するのが現実的です。
一方で、業務要件によっては、利用者本人の Windows 権限でファイルサーバーへ行く必要がない場合もあります。
その場合は、こういう設計の方がシンプルです。
利用者の認証・認可はアプリケーションで行う
↓
ファイルサーバーへのアクセスは専用サービスアカウントで行う
↓
操作ログに利用者IDと対象ファイルを記録する
これは「OS の ACL を最終判断にする」のではなく、「アプリケーションの認可を最終判断にする」設計です。
どちらが正しいかは業務要件次第です。
ただし、どちらの方式を選んでいるのかを明確にしないと、偽装、委任、ACL、アプリ認可が混ざって分かりにくくなります。
19. トークンハンドルのライフタイム
トークンはカーネルオブジェクトへのハンドルなので、取得したら不要になった時点で閉じる必要があります。
.NET では SafeAccessTokenHandle を使い、using でスコープを管理するのが基本です。
using SafeAccessTokenHandle token = NativeMethods.Logon(userName, domain, password);
string result = WindowsIdentity.RunImpersonated(token, () =>
{
return File.ReadAllText(path);
});
良くない例です。
// 良くない例: トークンをグローバルに保持し続ける
private static SafeAccessTokenHandle? _cachedToken;
トークンを長期間保持すると、こんな問題につながります。
- ハンドルリーク
- どの処理がどのトークンを使っているか分からなくなる
- アカウント無効化や権限変更との整合性が分かりにくくなる
- 認証情報を長く保持する設計になりやすい
- 監査上説明しにくい
原則はこうです。
必要なときに取得する
↓
最小範囲で使う
↓
必ず閉じる
もちろん、認証コストや運用要件によってはキャッシュを検討する場合もあります。 ただし、その場合でも、有効期限、破棄、アカウント変更、監査ログ、権限変更時の扱いを設計に含める必要があります。
20. 監査ログに残すべきこと
偽装を使う処理では、ログ設計も重要です。
最低限、この表の情報を残せるようにしておくと、後から調査しやすくなります。
| 項目 | 例 |
|---|---|
| 要求した利用者 | DOMAIN\alice |
| 実行プロセスのアカウント | DOMAIN\svc-app |
| 偽装したアカウント | DOMAIN\alice または専用アカウント |
| 対象リソース | ファイルパス、共有名、レジストリキーなど |
| 操作 | Read、Write、Delete、CreateProcess など |
| 結果 | Success、AccessDenied、Timeout、UnexpectedError |
| エラーコード | Win32 error code、HRESULT、例外種別 |
| 偽装スコープ | どのメソッド、どの操作単位で偽装したか |
ただし、ログに出してはいけないものもあります。
- パスワード
- アクセストークンの値
- 認証ヘッダー
- Kerberos チケットや資格情報そのもの
- 個人情報を含むファイル内容
ログの目的は、「誰の要求により、どのアカウントとして、何を試み、どう失敗または成功したか」を後から追えることです。
資格情報そのものを記録する必要はありません。
21. よくあるアンチパターン
偽装まわりでよく見る危ない実装を整理します。
21.1 アプリ全体を偽装する
WindowsIdentity.RunImpersonated(token, () =>
{
RunEntireApplication();
});
アプリ全体を偽装すると、どの操作がどの権限で実行されるのか分からなくなります。
偽装は、必要な I/O や特定 API 呼び出しに限定します。
21.2 finally なしで戻す
ImpersonateLoggedOnUser(token);
DoWork();
RevertToSelf();
例外時に戻らないため危険です。
必ず try / finally か、RunImpersonated を使います。
21.3 偽装中に fire-and-forget する
WindowsIdentity.RunImpersonated(token, () =>
{
_ = Task.Run(DoWorkAsync);
});
処理完了前に偽装スコープを抜けます。この書き方だと、投げたタスクは(14章のとおり)偽装されたまま動き続けるので、コード上の偽装区間と実際の偽装区間がずれます。しかも誰も await していないため、失敗しても例外が出ません。Win32 で直接偽装している場合は逆に、プロセスのアカウントで動きます。
偽装が必要なら RunImpersonatedAsync の中で await します。
21.4 ユーザー名だけで成功判定する
Console.WriteLine(WindowsIdentity.GetCurrent().Name);
ユーザー名が期待どおりでも、偽装レベルや権限が足りないことがあります。
ImpersonationLevel、対象 ACL、ログオン種別、ネットワーク委任も見ます。
21.5 管理者アカウントを便利アカウントとして偽装する
「この処理は失敗してほしくないから、管理者アカウントで偽装する」という設計は危険です。
必要最小限の権限を持つ専用アカウントを用意し、操作単位を絞る方が安全です。
21.6 パスワードを設定ファイルに置く
{
"UserName": "DOMAIN\\admin",
"Password": "P@ssw0rd!"
}
これは避けるべきです。
資格情報を扱うなら、Secret Store、Windows Credential Manager、DPAPI、クラウドの Key Vault、運用基盤のシークレット管理など、環境に合った仕組みを使います。
21.7 プロセス起動とファイルアクセスを同じ話として扱う
ファイルアクセスだけなら偽装トークンで足りる場合があります。
一方、別ユーザーとしてプロセスを起動したいなら、プライマリトークン、プロファイル、デスクトップ、環境変数、セッション、権限など、別の論点が出ます。
CreateProcessAsUser や CreateProcessWithTokenW を使う場合は、偽装とは別の設計として扱うべきです。
22. テストの観点
偽装処理は、手元の管理者環境だけで確認すると見落としが出ます。
最低限、これだけのテストケースを用意します。
| ケース | 確認すること |
|---|---|
| 権限ありユーザー | 対象ファイルを読める / 書ける |
| 権限なしユーザー | Access denied として正しく失敗する |
| 存在しないユーザー | 認証失敗として扱える |
| パスワード誤り | ログに秘密情報を出さず失敗する |
| ネットワーク共有 | ローカルと UNC の挙動差を確認する |
| 非同期処理 | await の後も期待した範囲で動く |
| 例外発生 | 偽装が必ず解除される |
| 並行リクエスト | 他ユーザーの偽装が混ざらない |
| サービス実行 | 開発者の対話ログオン環境ではなく、実際のサービスアカウントで動く |
特に、この 2 つは外せません。
成功すること
失敗すべきときに失敗すること
偽装処理では、成功ケースだけでなく、権限がないユーザーで確実に拒否されることもテストします。
拒否されるべき操作が成功してしまうなら、偽装や認可の設計が間違っている可能性があります。
23. 実装チェックリスト
実装前後に、このチェックリストを確認します。
| 観点 | チェック内容 |
|---|---|
| 目的 | なぜ偽装が必要なのか説明できるか |
| 代替案 | サービスアカウントやアプリ認可ではだめか検討したか |
| 範囲 | 偽装スコープは最小か |
| 戻し | 例外時にも確実に戻るか |
| 非同期 | RunImpersonatedAsync 内で完了まで await しているか |
| トークン | プライマリトークンと偽装トークンを混同していないか |
| 偽装レベル | Identification と Impersonation / Delegation の違いを見ているか |
| ネットワーク | UNC、ダブルホップ、Kerberos 委任の要否を確認したか |
| UAC | 管理者所属と昇格済みを混同していないか |
| 秘密情報 | パスワードを平文保存していないか |
| ハンドル | SafeAccessTokenHandle を using で閉じているか |
| ログ | 利用者、偽装先、対象、結果を追えるか |
| テスト | 権限あり / なし / 例外 / 並行処理を確認したか |
このチェックリストで引っかかる項目が多い場合は、コードを書く前に設計を見直した方がよいです。
24. 使いどころを見極める
偽装トークンは強力ですが、常に最初に選ぶべき手段ではありません。
設計としては、用途ごとに分けて考えると迷いにくくなります。
24.1 OS の ACL をそのまま使いたい場合
ファイルサーバーや共有フォルダーの ACL が業務ルールの中心であり、アプリケーションもその判断に従うべきなら、利用者本人としての偽装に意味があります。
利用者本人としてアクセス
↓
Windows の ACL が最終判断
この場合は、Windows 認証、偽装レベル、委任、ネットワーク構成まで含めて設計します。
24.2 アプリケーション側で認可したい場合
業務ルールがアプリケーション側にあり、ファイルサーバーや DB はアプリケーションの管理下にあるなら、サービスアカウントでアクセスし、アプリケーション側で認可する方が分かりやすいことがあります。
利用者を認証
↓
アプリケーションで認可
↓
サービスアカウントでリソースへアクセス
↓
監査ログに利用者IDを残す
この方式では、偽装を使わない代わりに、アプリケーションの認可ロジックと監査ログが重要になります。
24.3 管理操作をしたい場合
管理操作を利用者のトークンで直接実行するより、管理操作専用のサービスや API を用意し、そこで認可、入力検証、監査、ロールバックを行う方が安全なことが多いです。
クライアント
↓
管理APIに要求
↓
管理APIが認可
↓
必要最小限の権限で操作
↓
監査ログ
「とりあえず管理者を偽装する」は、短期的には楽に見えます。
しかし、長期的には、監査、障害調査、権限変更、セキュリティレビューで苦しくなります。
25. まとめ
Windows の偽装トークンは、Windows の権限管理を正しく使うための重要な仕組みです。
ただし、理解せずに使うと、成功しているように見えて危険なコードになりやすい分野でもあります。
押さえるべきポイントを並べておきます。
- アクセストークンは、ユーザー、グループ、権限などのセキュリティコンテキストを表す
- プロセスにはプライマリトークンがある
- 偽装トークンは主にスレッドに付き、アクセスチェックで使われる
- 偽装は権限昇格ではない
- プライマリトークンと偽装トークンは用途が違う
- 偽装レベルによって、識別だけできるのか、実際にアクセスできるのか、リモートへ委任できるのかが変わる
- Win32 API で偽装するなら、必ず
try/finallyでRevertToSelfする - .NET では
WindowsIdentity.RunImpersonated/RunImpersonatedAsyncでスコープを小さく表現する LogonUserを使う場合は、資格情報管理とログオン種別に注意する- ネットワーク共有では、偽装だけでなく Kerberos 委任やサービスアカウント設計も関係する
- トークンハンドルは
SafeAccessTokenHandleとusingで管理する - 成功ケースだけでなく、拒否されるべきケースもテストする
偽装トークンの実装で大事なのは、「別ユーザーで動いた」という一点ではなく、こうしたことを説明できる状態にしておくことです。
どの処理を
誰の要求で
どのアカウントとして
どの範囲だけ実行し
どこで元に戻し
成功と失敗をどう記録しているか
ここまで整理できていれば、偽装は怖い仕組みではありません。
Windows の ACL、サービスアカウント、既存ファイルサーバー、社内ドメイン資産を活かすための、実務的な道具として使えるようになります。
参考
- この記事のサンプルコード一式(ライブラリ、デモ、ユニットテスト)
https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/windows-impersonation-token - Microsoft Learn: Access Tokens
https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens - Microsoft Learn: Impersonation Tokens
https://learn.microsoft.com/en-us/windows/win32/secauthz/impersonation-tokens - Microsoft Learn: Impersonation Levels
https://learn.microsoft.com/en-us/windows/win32/secauthz/impersonation-levels - Microsoft Learn:
SECURITY_IMPERSONATION_LEVELenumeration
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level - Microsoft Learn:
ImpersonateLoggedOnUserfunction
https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-impersonateloggedonuser - Microsoft Learn:
RevertToSelffunction
https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-reverttoself - Microsoft Learn:
LogonUserfunction
https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera - Microsoft Learn:
DuplicateTokenExfunction
https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex - Microsoft Learn:
TOKEN_INFORMATION_CLASSenumeration
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class - Microsoft Learn:
WindowsIdentity.RunImpersonated
https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonated - Microsoft Learn:
WindowsIdentity.RunImpersonatedAsync
https://learn.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonatedasync - .NET ランタイムの実装:
WindowsIdentity.cs(偽装トークンを保持するAsyncLocalと、スレッド切り替え時に偽装を張り直すCurrentImpersonatedTokenChanged)
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs - Microsoft Learn: Configure Windows Authentication in ASP.NET Core
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth - Microsoft Learn: Kerberos Constrained Delegation Overview
https://learn.microsoft.com/en-us/windows-server/security/kerberos/kerberos-constrained-delegation-overview - Microsoft Learn:
ProtectedDataClass
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windows I/Oの深層(第4回) ── キャッシュマネージャー:あなたのWriteFileはいつディスクに届くのか
Windowsのキャッシュマネージャーを図解で解説する連載の第4回です。ファイルマッピングとして実装されたキャッシュ、先読みと遅延書き込み、FlushFileBuffersやFILE_FLAG_NO_BUFFERINGの使い分け、電源断でデータが消える条件までを整理します。
Windows I/Oの深層(第3回) ── I/O完了ポート(IOCP)と.NETスレッドプール:async/awaitの地下室
I/O完了ポート(IOCP)を図解で解説する連載の第3回です。完了キューとスレッド数制御を一体化した設計、コンカレンシー値とLIFO解放、.NETスレッドプールとasync/await継続の実行スレッドまでを整理します。
Windows I/Oの深層(第2回) ── 同期I/Oと非同期I/O:OVERLAPPEDの本当の意味
Windowsの同期I/Oと非同期I/O(オーバーラップI/O)を図解で解説する連載の第2回です。FILE_FLAG_OVERLAPPEDの意味、完了通知の4方式、非同期のはずが同期完了する条件、キャンセルの作法、.NETとの対応までを整理します。
Windows I/Oの深層(第1回) ── すべての読み書きはIRPになる:I/Oシステムの全体像
WindowsのI/Oシステムを底から解説する連載の第1回です。オブジェクトマネージャーの名前空間、ドライバー・デバイス・ファイルの3つのオブジェクト、IRPのライフサイクル、CloseHandleの裏側までを図解で整理します。
Windows PCを廃棄する前にやっておきたいこと ── データ消去・アカウント解除・バックアップの実務チェックリスト
Windows PCを廃棄・譲渡・売却・リース返却する前にやっておきたいことを、バックアップ、データ消去、BitLocker、Microsoftアカウント、OneDrive、仕事用アカウント、開発者PC特有の秘密情報、廃棄証跡の観点から整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
既存資産活用・移行支援
COM / ActiveX / OCX、32bit / 64bit 制約を抱える既存資産の活用と移行を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- Windowsの偽装トークンとは何ですか?
- 偽装トークンは、スレッドが別のセキュリティコンテキストでアクセスチェックを受けるためにスレッドに付くトークンです。プロセス全体が別ユーザーになるのではなく、偽装したスレッドだけがそのユーザーの権限でファイルやレジストリなどのアクセスチェックを受けます。「管理者になる魔法」つまり権限昇格ではなく、サーバープロセスの一部の処理だけをクライアントユーザーの権限でアクセスチェックさせる仕組みです。
- プライマリトークンと偽装トークンはどう違いますか?
- プライマリトークンはプロセスのセキュリティコンテキストを表し、CreateProcessAsUserなどのプロセス起動に使います。偽装トークンはスレッドが別のセキュリティコンテキストで動くために使い、ImpersonateLoggedOnUserやNamed Pipeのクライアント偽装で登場します。プロセスを起動したい場合は原則プライマリトークンが必要で、偽装トークンしかない場合はDuplicateTokenExでプライマリトークンを作る流れになります。混同するとAccess deniedなどのエラーで迷うことになります。
- 偽装しているのにAccess deniedになるのはなぜですか?
- 確認すべき観点は複数あります。偽装スコープ内でWindowsIdentity.GetCurrent()のNameだけでなくImpersonationLevelも確認し、IdentificationレベルではなくImpersonation以上になっているかを見ます。対象リソースのACL、実際のI/Oが偽装スコープ外や別タスクで動いていないか、ローカルは成功してUNCだけ失敗するダブルホップ問題ではないか、UACによる非昇格トークンの影響も確認します。名前が期待どおりでも権限が足りないことがあります。
- .NETで安全に偽装を実装するにはどうすればよいですか?
- WindowsIdentity.RunImpersonated / RunImpersonatedAsyncを使い、偽装スコープをラムダ式の中に閉じ込めるのが基本です。偽装が必要な非同期処理はRunImpersonatedAsyncの中でawaitし、偽装スコープの内側からfire-and-forgetのタスクを投げないようにします。Win32 APIで偽装する場合は必ずtry/finallyでRevertToSelfを呼びます。偽装スコープは必要なI/Oだけに最小化し、トークンハンドルはSafeAccessTokenHandleとusingで管理します。