Windows NTでアプリを実行するユーザーを制御する方法

戻る

WindowsNTでアプリケーションを作成するとき,実行するユーザを限定させたいことが よくあります.たとえば,「システムの動作に関係があるものは Administraor グループのユーザ以外に操作させたくない」ときや,「特定のユーザ以外が実行する ことを禁止したい」といった場面が考えられます.
デスクトップの環境を制御するには,システムポリシーエディタを利用することが できますが,コントロールパネルアプリケーションなど,通常の形態の アプリケーションでは利用することができません.
Windows95でもシステムポリシーエディタがありますので,ユーザごとのデスクトップ 環境をある程度構成することができますが,ファイルのセキュリティやユーザ管理は WindowsNTと比較すると,サポートされていないと言ってもよい状態です.
本章ではWindowsNTでサポートされているセキュリティ機能を利用することを前提に にします.

12-1 実際にはどうすればよいか

WindowsNTで,アプリケーションのセキュリティ問題を解決するにはいくつかの方法が 考えられます.
1) レジストリにアクセス権を設定してアクセスできるユーザであれば実行する
2) インストール時にHKEY_CURRENT_USERに情報を書き込み,実行時に情報があるときだけ処理を行う
3) ユーザ名をチェックする
4) ユーザが属しているグループをチェックする
5) ファイルやディレクトリに特定のユーザやグループのみにアクセス権を付ける
これらの方法は一長一短で状況によっては有効なものもありますが,前提条件によって は利用できないものや確実性に欠けます.しかし,これらの情報を組み合わせたり アプリケーションや利用者によっては前提条件をつけることで実用レベルに達する チェック方法にすることができます.

12-2 レジストリのアクセス権を利用する

サービスの設定を,コントロールパネルアプリケーションで行わせる場合, 実行しているユーザをチェックして設定を行わせるかどうかをチェックしているもの があります.しかし,サービスの設定を行う権限を与えるための処理を特に行わず, レジストリのアクセス権があれば誰でも設定できるようにしておくことにより, レジストリにアクセスできるユーザは設定を行ってもよいユーザであるとして おきます.
この方法では,レジストリエディタで簡単に利用できるユーザを変更させることができ ます.逆にそのような方法で利用できるユーザを変更できると困る場合には,この方法 は適していません.

12-3 レジストリの情報を書き込む場所を工夫する

特定のユーザのみが設定を行うことができるようにするには,アプリケーションを インストールするときに,HKEY_CURRENT_USER に情報を設定しておきます.
通常このキーは,ユーザ固有の設定情報を格納するために用いられます. たとえば,ウィンドウのサイズや位置など利用するユーザ固有の情報を格納しておく ことにより,各ユーザが別々の設定でアプリケーションを実行することができるように なります.
このキーに設定された値は,他のユーザがログオンしても見ることができないため, 値が設定されていないユーザは実行できないようにしておくことで,特定のユーザのみ が実行できるようになります.

12-4 SIDから情報を取得する

ユーザやグループの情報を取得するには,SID(セキュリティ識別子)について 理解しておく必要があります. ここでは,ユーザ名やグループを取得する方法を説明する前に,SIDについて簡単に 説明したいと思います.
WindowsNT内でユーザやグループは,SIDで管理されています.
このSIDは,可変長で必ず一意な値が利用されます.SIDは,ログオン時にユーザーに 割り当てられ、 そのユーザーが起動するすべてのプロセスに付属するアクセストークン に利用されます.SIDは48ビットの識別子オーソリティ,リビジョン レベル, 可変個数のサブオーソリティ(相対識別子)を持ち,それぞれが同じRIDを発行して も、 同じSIDが生成されません.

表12-1:SID(セキュリティ識別子)


S-R-I-S-S...
^ ^ ^ ^ ^
| | | | |
+------------ SIDの数字列の開始を示す
  | | | |
  +---------- リビジョンレベル
    | | |
    +-------- 識別子オーソリティ
      | |
      +-+---- サブオーソリティ

(a)SIDの標準簡略表記法


NULL SID         (S-1-0-0) メンバを持たないグループ
ワールド         (S-1-1-0) すべてのユーザーを含むグループ
ローカル         (S-1-2-0) 物理的に接続されている端末からログオンしている
                           ユーザー
作成者オーナーID (S-1-3-0) 新しいオブジェクトを作成したユーザーのセキュリティ
                           識別子で置き換えられるセキュリティ識別子
作成者グループID (S-1-3-1) 新しいオブジェクトを作成したユーザーのプライマリ
                           グループSIDで置き換えられるセキュリティ識別子

(b)汎用の既知SID

    SECURITY_NULL_SID_AUTHORITY
    SECURITY_WORLD_SID_AUTHORITY
    SECURITY_LOCAL_SID_AUTHORITY
    SECURITY_CREATOR_SID_AUTHORITY
    SECURITY_NT_AUTHORITY

(c)識別子オーソリティ


    SECURITY_NULL_SID_AUTHORITY
    SECURITY_WORLD_SID_AUTHORITY
    SECURITY_LOCAL_SID_AUTHORITY
    SECURITY_CREATOR_SID_AUTHORITY
    SECURITY_NON_UNIQUE_AUTHORITY

(d)相対識別子オーソリティ


    DOMAIN_USER_RID_ADMIN
    DOMAIN_USER_RID_GUEST
    DOMAIN_GROUP_RID_ADMINS
    DOMAIN_GROUP_RID_USERS
    DOMAIN_GROUP_RID_GUESTS

(e)ドメイン相対のRID


    DOMAIN_ALIAS_RID_ADMINS
    DOMAIN_ALIAS_RID_USERS
    DOMAIN_ALIAS_RID_GUESTS
    DOMAIN_ALIAS_RID_POWER_USERS
    DOMAIN_ALIAS_RID_ACCOUNT_OPS
    DOMAIN_ALIAS_RID_SYSTEM_OPS
    DOMAIN_ALIAS_RID_PRINT_OPS
    DOMAIN_ALIAS_RID_BACKUP_OPS
    DOMAIN_ALIAS_RID_REPLICATOR

(f)ローカルグループRID


12-5 ユーザ名を取得する

ユーザ名を取得することにより,あらかじめインストール時などにユーザ名を保存して おき,そのユーザしか実行できないようにするといった方法が考えられます.
また,Win32 API LookupAccountSid では,ドメイン名もいっしょに取得できますので, NT Server のドメインコントローラを利用しているときには,ドメイン名を利用する こともできるでしょう.なお,ユーザ名だけを取得するときには, Win32 API GetUserName を利用します.
 レジストリやファイルに対してアクセス権を設定するには,SIDが必要となります. 各ユーザアカウントのSIDを取得するためには, Win32 API LookupAccountName で ユーザ名が必要となりますので,ユーザ名を取得する方法を知っておくと何かと役に 立つのではないかと思います.


リスト12-1:ユーザ名とドメイン名を取得する


    __try{
        fResult = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
        if(fResult == FALSE){
            __leave;
        }
        fResult = GetTokenInformation(hToken,
                TokenUser, szBuff, dwBuffSize, &dwBuffSize);
        if(fResult == FALSE){
            __leave;
        }
    }
    __finally{
        if(hToken){
            CloseHandle(hToken);
        }
    }
    if(fResult == FALSE){
        return;
    }

    LookupAccountSid(NULL, ((PTOKEN_USER)szBuff)->User.Sid,
        szUserName, &dwUserNameSize, szDomainName, &dwDomainNameSize,
        &snu);


表12-2:ユーザ名を取得するために利用するAPI
BOOL OpenProcessToken(
 HANDLE hProcess, DWORD dwAccessMask, LPHANDLE lphToken)
プロセスに関連付けられたアクセストークンをオープンする
引数
HANDLE hProcess ... アクセストークンをオープンするプロセス
DWORD dwAccessMask ... アクセストークンに必要なアクセスの種類を示すアクセスマスク
 TOKEN_ADJUST_DEFAULT ... デフォルトACL
 TOKEN_ADJUST_GROUPS ... グループ変更
 TOKEN_ADJUST_PRIVILEGES ... 特権を変更
 TOKEN_ALL_ACCESS ... すべてのアクセス権を組み合わせ
 TOKEN_ASSIGN_PRIMARY ... プライマリトークンを設定
 TOKEN_DUPLICATE ... トークンを複製する
 TOKEN_EXECUTE ... STANDARD_RIGHTS_EXECUTE
          | TOKEN_IMPERSONATE
 TOKEN_IMPERSONATE ... 偽装アクセストークンを付加する
 TOKEN_QUERY ... 問い合わせ
 TOKEN_QUERY_SOURCE ... トークンのソースを問い合わせ
 TOKEN_READ ... STANDARD_RIGHTS_READ
TOKEN_QUERY
 TOKEN_WRITE ... STANDARD_RIGHTS_WRITE
         | TOKEN_ADJUST_PRIVILEGES
         | TOKEN_ADJUST_GROUPS
         | TOKEN_ADJUST_DEFAULT
LPHANDLE phToken ... トークンを識別するハンドルを格納する領域を指すポインタ

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL GetTokenInformation(
 HANDLE hToken, TOKEN_INFORMATION_CLASS tic,
 LPVOID lpvReturnedInformation, DWORD dwReturnedInformation,
 PDWORD ldwReturnRequired)
アクセストークンの情報を取得する
引数
HANDLE hToken ... アクセストークンハンドル
TOKEN_INFORMATION_CLASS tic ... 取得する情報の種類を識別する
 TOKEN_INFORMATION_CLASS列挙型変数
  TokenUser
  TokenGroups
  TokenOwner
  TokenPrimaryGroup
  TokenDefaultDacl
  TokenType
  TokenImpersonationLevel
  TokenStatistics
LPVOID lpvReturnedInformation ... バッファ
DWORD dwReturnedInformation ... バッファサイズ
PDWORD ldwReturnRequired ... バッファに必要な実際のバイト数

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL LookupAccountName(LPCTSTR lpszSystem, LPCTSTR lpszAccount,
 PSID lpsid, LPDWORD lpdwSid, LPTSTR lpszReferencedDomain,
 LPDWORD lpdwReferencedDomain, PSID_NAME_USE psnu)
システムとアカウントの名からSIDとドメイン名を取得

引数
LPCTSTR lpszSystem ... システムを示す文字列
  ローカルシステムはNULLを指定する
LPCTSTR lpszAccount ... アカウント名を示す文字列
PSID lpsid ... アカウント名に対応するSID構造体が返されるバッファを指す
LPDWORD lpdwSid ... バッファのサイズを指定する
LPTSTR lpszReferencedDomain ... アカウント名が存在するドメインの名前を格納する領域を指す
LPDWORD lpdwReferencedDomain ... バッファのサイズを文字単位で指定する
PSID_NAME_USE psnu ... アカウントの種類を示すSID_NAME_USE列挙型の値を指すポインタ

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL LookupAccountSid(LPCTSTR lpszSystem, PSID lpsid,
 LPTSTR lpszAccount, LPDWORD lpdwAccount,
 LPTSTR lpszReferencedDomain, LPDWORD lpcchReferencedDomain,
 PSID_NAME_USE psnu)
SIDからアカウント名とドメインの名を取得

引数
LPCTSTR lpszSystem ... システム名文字列を指す
PSID lpsid ... セキュリティ識別子を指す
LPTSTR lpszAccount ... アカウント名文字列を指す
LPDWORD lpdwAccount ... アカウント文字列のサイズを指す
LPTSTR lpszReferencedDomain ... 参照ドメイン名を指す
LPDWORD lpcchReferencedDomain ... ドメイン文字列のサイズ
PSID_NAME_USE psnu ... SIDの種類の構造を指す

戻り値
正常終了 TRUE
異常終了 FALSE
BOOL GetUserName(LPSTR lpszBuff, LPDWORD lpdwBuffSize)
現在のスレッドのユーザー名を取得

引数
LPTSTR lpszBuff ... 名前バッファを指す
LPDWORD lpdwBuff ... 名前バッファのサイズを指す

戻り値
正常終了 TRUE
異常終了 FALSE

12-6 ユーザの属しているグループを取得する

基本的にユーザ名と同様の考え方で利用できますが,グループを指定するとその グループに属している複数のユーザーアカウントをまとめて指定することができます のでユーザアカウントを直接相手にするよりは設定が簡単になります.
ファイルや共有ディレクトリに対してセキュリティを設定するときには,グループ指定 で設定するほうが多いのではないかと思われます.グループもSIDを取得すれば, ユーザアカウントと同様にセキュリティの設定に利用することができます.
また,ログオンしているユーザがシステム管理者であるかを調べるには,ユーザが Administrator グループに属しているかを調べることで知ることができます.
実際には,プロセスのSIDに設定されているRID(リビジョン識別子)をチェックする ことで,ユーザーがどのグループに属しているかを調べることができます.

12-7 ファイルのアクセス権を設定する
EXEファイルに特定のユーザや特定グループのユーザのみに実行権を与えることにより, 特定ユーザのみがアプリケーションを実行させることができるようになります. アクセス権のないユーザが実行すると,システムがエラーダイアログボックスを表示 させアプリケーション自身は実行されないため,アプリケーション自身がエラー処理 を行うことができません.この方法で実行を抑制するよりは,アプリケーションを格納 しているディレクトリやスタートボタンで表示されるグループのディレクトリ (\winnt\profiles\user\スタート メニュー\プログラム\....) にアクセス権を設定して, 特定ユーザしか見えないようにしておいたほうが格好がよいでしょう.
この方法の欠点は,ファイルを格納するディスクは必ずNTFSでフォーマットされている 必要があることです.Windows95とWindowsNTが共存しているシステムでは,共通に利用 するドライブはFATを利用しますので,作成するアプリケーションが必ずNTFSでフォーマットされたドライブにインストールされるという前提条件がないときには,この方法を 利用することができません.
ファイルに対してアクセス権を設定は,次の手順で行います. この手順は,利用するAPI に多少の違いがありますが,レジストリに対しても同様に 行うことができます.
(* ファイルにアクセス権を設定する方法は,第10章を参照)

12-8 プログラムについて

ユーザの情報を取得してウィンドウに表示させるアプリケーションです. WindowsNT専用ですので,Windows95 ではすべての情報を表示することができません. 起動すると,ウィンドウにコンピュータ名,ユーザ名,ドメイン名,ユーザが所属する グループを表示します.
余談になりますが,WindowsNT4.0では,プログラムマネージャは利用されなくなり, 以前のバージョンのようにプログラムマネージャのタイトルバーで現在のユーザ名を 確認することができなくなってしまいました.このサンプルを応用して,タスクバー のアイコンにユーザ名を表示させるツールを作成すれば便利ではないかと 思います.

●ソースファイル
WHO.EXE
 WHO.C (ソースファイル)
 WHO.H (ヘッダファイル)
 WHO.RC (リソースファイル)
 WHO.ICO (アイコンファイル)
 MAKEFILE (メイクファイル)


コーヒーブレーク◆Win64に向けて

現在のWindowsは32ビットOSですが,MSDN Library には64ビットCPUに対応したAPIに 関する資料が整備されつつあります.
OSが64ビット化されても,Win32アプリケーションは動作するでしょう. しかし,Windows95の発表によってアプリケーションの32ビット化ラッシュが起こった ように,「Win64対応」も必須になるでしょう.
もし,開発中の32ビットアプリケーションを64ビット化することが予想されるので あれば,今のうちにWin64の情報を入手して,研究しておくとよいでしょう.

MSDN Library
 Platform SDK: Win64 Programming Preview
  Getting Ready for 64-bit Windows
  Designing 64-bit-Compatible Interfaces