管理者権限処理の分離実装【Windowsアプリ】

· · Windows開発, セキュリティ, UAC, C# / .NET, Win32

結論

Windowsアプリで管理者権限が必要な処理がある場合、アプリ全体を管理者として動かすのではなく、管理者権限が必要な部分だけを別EXEに分離するのが安全かつ実務的です。

  • UIアプリは asInvoker のまま動かす
  • 管理者処理は別EXE(helper)に切り出し、requireAdministrator にする
  • helperの起動は runas を使う
  • helperとの通信は名前付きパイプなどのIPCを使う
  • helperに渡すのは型付きの要求だけにする
  • helper側で要求内容を再検証する

前提:同じプロセスの一部だけを管理者化できない

WindowsのUACはプロセス単位です。同じプロセスの中で「この処理だけ管理者権限」はできません。管理者処理が必要なら、別プロセスやサービスなど別の実行単位に切り出します。

分離モデルの選び方

モデル 説明 向いている場面
Administrator Broker UI(asInvoker) + 管理者helper EXE 必要な瞬間だけUACを出せばよい場合
OS Service UI + 常駐サービス 常時稼働の管理機能
Elevated Task UI + 管理者権限タスク 1回ごとの定型処理
COM Object UI + 昇格COM 既存COM設計がある限定的な場合

最初に検討しやすいのは broker EXE です。設定画面の特定ボタンを押した時だけ管理者権限が必要になるようなケースに最適です。

おすすめ構成

[MyApp.exe]  asInvoker
     |
     | runas
     v
[MyApp.AdminBroker.exe]  requireAdministrator
     |
     | named pipe
     v
[管理者操作を実行]

ポイント:

  1. UIプロセスは最後まで非昇格
  2. 管理者helperは短命
  3. helperが受け付ける操作は固定の許可リストのみ

実装で守るべきルール

helperは「なんでも屋」にしない

ダメな例:UIからhelperに生のコマンド文字列(reg add ... など)を渡す。これだとUIが侵害されたらhelperも一緒に壊れます。

良い例:操作名を固定する(set-explorer-context-menuinstall-service など)。引数も boolenum、数値、限定された文字列に絞ります。

起動経路の注意点

  • helper EXEのパスは絶対パスで指定する
  • .NETでは UseShellExecute=true を明示する(Verb="runas"UseShellExecute=true でのみ有効)
  • runas と標準入出力リダイレクトは相性が悪いので、IPCは名前付きパイプを使う

名前付きパイプのセキュリティ

  • 既定のACLに頼らず、明示的な PipeSecurity を設定する
  • PipeOptions.CurrentUserOnly は非昇格UIと昇格helperの通信には使えない(昇格レベルも確認するため)
  • UI側で自分のSIDを取得してhelperに渡す
  • helper側ではUIユーザーSIDだけにpipe接続権を与える
  • GetNamedPipeClientProcessId で接続元PIDも確認する

PID検証について

PID検証は「同じユーザーの別プロセスが先に接続する」のを防ぐ追加防御です。ただしPIDが一致していてもUIが侵害されていれば危険な要求が届くため、operation許可リストと引数検証が本質的な防御です。

具体的なコード例

共通契約(BrokerProtocol)

操作名とリクエスト/レスポンス型を別プロジェクトで定義します。

public static class BrokerOperations
{
    public const string SetExplorerContextMenu = "set-explorer-context-menu";
}

public sealed record BrokerRequest(string Operation, JsonElement Payload);
public sealed record BrokerResponse(bool Success, string? ErrorCode, string? Message);

pipeにはJSONを長さ付きで送ります(ヘッダ4バイト+ペイロード)。

UI側(ElevationBrokerClient)

  • helper EXEの起動は ProcessStartInfoUseShellExecute=true, Verb="runas" を指定
  • ランダムなpipe名を生成し、自分のPIDとSIDをhelperに渡す
  • pipe接続後に型付きリクエストを送信

helper側(AdminBroker)

  • 起動引数からpipe名、client PID、client SIDを受理
  • pipeのACLを明示的に組み立てる(呼び出し元UIユーザーSIDに接続権を付与)
  • 接続後にclient PIDを検証
  • switch (request.Operation) で固定操作のみdispatch

管理者操作の実装例

たとえばExplorerの右クリックメニューをレジストリに登録する処理では:

  • UIから任意のレジストリパスを受け取らない
  • UIから任意のコマンド文字列を受け取らない
  • 登録対象EXEはhelper側で固定解決
  • リクエストの内容は Enabled(bool)だけ

これでhelperは「Explorer右クリックメニューの登録状態を切り替える」という1つの意味しか持ちません。

よくあるNGパターン

  1. UI全体を requireAdministrator にする — 権限境界を雑に潰す
  2. helperに生の文字列コマンドを渡す — helperが汎用実行口になる
  3. 名前付きパイプの既定ACLをそのまま使う — 明示的にACLを設定すべき
  4. CurrentUserOnly に飛びつく — 非昇格UI↔昇格helperには向かない
  5. helperが任意パスを受け取って操作する — 操作は必ず固定化する

まとめ

  • UIは asInvoker、helperは requireAdministrator
  • 通信は名前付きパイプ、pipe ACLとclient PIDで接続元を絞る
  • helperは固定operationしか受けず、引数を再検証する
  • この設計は後からservice化する際にも移行しやすい

参考資料

関連する記事

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

Windowsアプリのセキュリティチェックリスト

WPF・WinForms・WinUI・C++・C# など Windows アプリ開発で最低限外したくないセキュリティ項目を、権限・署名・秘密情報・通信・入力・DLL・ログの観点でチェックリスト化し、リリース前に何を確認すべきか整理した記事です。

記事を読む

Windows管理者特権の必要/不要の境界

Windowsで管理者特権が必要になる場面を、UAC、保護領域、サービス、ドライバ、per-user/per-machine 配布の観点で整理し、無駄な昇格を減らす設計の見分け方と典型例、よくある誤解までを実務向けにまとめます。

記事を読む

関連トピック

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

このテーマがつながるサービス

この記事は次のサービスページにつながります。近い入口からご覧ください。

ブログ一覧に戻る