・セキュリティ このメモでは、セキュリティについて述べる。ただし、偽装と監査については省略 。 ・アラインメント セキュリティ系のデータは、文字列を除いてDWORD境界に整列していなければなら ない。従って、アプリケーションがセキュリティ系のデータを作成するなどの場合 は、コンパイラのオプション指定で変数の割り当てをDWORD境界にそろえるように するか、GlobalAllocなどのメモリ割り当て関数で割り当てたメモリを使用しなけ ればならない。 ・セキュリティ識別子(SID) Win32では、ユーザマネージャなどに見られるように、セキュリティを主にグルー プとユーザで管理している。このため、システムはそれぞれのグループやユーザに 対して、これをユニークに識別するためのIDを割り当てている。このIDをセキュリ ティ識別子(SID)と呼ぶ。あるユーザにあるSIDが割り当てられているとき、ユーザ 名からSIDを求めるにはLoopupAccountNameを使用する。逆にSIDからユーザ名を求 めるには、LookupAccoundSidを使用する SIDは、識別子オーソリティ値、リビジョンレベル、1〜8個のサブオーソリティ値 から構成される。識別子オーソリティ値は、更にSIDの作成元を識別する値と、そ の作成元でユニークであるIDに分けられる。後者を相対識別子(RID)と呼ぶ。いく つかのSIDやRIDが、システムで既に定義されている。 SIDの最後のサブオーソリティを除いたものを、SIDのプリフィックスと呼ぶ。 SIDは、通常アプリケーションが直接アクセスできる形式では表されていない。ア プリケーションがSIDの情報を取得・設定するためには、いくつかの関数を使用し なければならない。 SIDが正しいものかどうかを判定するには、IsValidSidを使用する。 SID中を構成する各要素を取得するには、次のような関数を使用する。SID中の識別 子オーソリティの位置を取得するには、GetSidIdentifierAuthorityを使用する。S ID中の特定のサブオーソリティの位置を取得するには、GetSidSubAuthorityを使用 する。SID中に含まれるサブオーソリティの個数を取得するには、GetSidSubAuthor ityCountを使用する。 SIDに関する判定を行うには、以下のような関数を使用する。EqualSidは、二つのS IDが等しいかどうかを判定する。EqualPrefixSidは、二つのSIDのプリフィックス が等しいかどうかを判定する。 SIDの複製を行うには、まずGetLengthSidで複製元のSIDのサイズを取得した後に、 複製先バッファを用意し、CopySidでSIDを複製する。 SIDを新規に作成するには、AllocateAndInitializeSidを使用する。作成したSIDは 、FreeSidで開放する。他の作成方法としては、GetSidLengthRequiredで作成するS IDに必要なバッファのサイズを求めた後に、バッファを用意し、InitializeSidで オーソリティ値とサブオーソリティの個数を設定し、最後に各々のサブオーソリテ ィ値を設定していく。 ・ログオンSID 現在ログオンしているユーザを識別するSIDは、ログオン識別子SIDと呼ばれる。こ のSIDは、他のSIDと異なり、ログオンごとに変化する。 ・LUID AllocateLocallyUniqueId ・Win32のセキュリティ 例えばあるプロセスがファイルなどのオブジェクトを使用したいとする。システム は、そのプロセスを生成したユーザとそのユーザが属しているグループの情報を取 得し、目的のオブジェクトが、そのユーザやグループに対してアクセスを許可して いるかどうかを判定する。判定に成功すると、プロセスはオブジェクトへの操作を 許されることになる。 このように、プロセスとオブジェクトはそれぞれセキュリティ情報を持っている。 プロセスの持つセキュリティ情報をアクセストークン(access token)、オブジェク トの持っている情報をセキュリティ記述子(security descriptor)と呼ぶ。 以下に例を挙げる。あるプロセスの持つアクセストークンは、主に以下のような情 報を持っている。 (1)プロセスの作成者は、広末 (2)作成者の属しているグループは、営業部 そのプロセスがアクセスしようとしているオブジェクトのセキュリティ記述子には 、主に以下のような情報がある。 (3)総務部グループに属している人に対して、書き込みアクセスを許可する。 (4)営業部グループに属している人に対して、読み込みアクセスを許可する。 (5)中居に対して、書き込みアクセスを許可する。 (6)香取に対して、書き込みアクセスを拒否する。 (7)企画部に対して、書き込みアクセスを許可する。 (それぞれのユーザやグループは、実際にはSIDで表されている) そのプロセスがオブジェクトを読み込みアクセスでオープンを試みたとする。シス テムは、セキュリティ記述子の中の、アクセスの許可・拒否のリストを頭から順に たどっていき、そのプロセスがアクセス可能かどうかをチェックする。この例では 、(4)で、アクセスが許可される。 また、読み書きアクセスでオープンを試みたとする。広末が営業部のみに属してい る場合、(4)で読み込みアクセスは許可されるものの、広末に対して書き込みアク セスを許可する項目が無いため、アクセスは最終的に拒否される。ただし、広末が 総務グループにも属している場合、アクセスは許可される。 このアクセスの許可・拒否のリストは、順番が重要である。システムは、頭から順 にリストをたどっていき、許可・拒否を受領した時点で、それ以降リストを調べる ことはない。従って、もし香取が企画部に属していたとしても、(6)の拒否項目で アクセスは拒否される。逆に、香取が営業グループに属していれば、(4)の許可項 目でアクセスは許可される。 このリストを、特にアクセス制御リスト(ACL)と呼ぶ。また、アクセス制御リスト 中の各項目を、アクセス制御エントリ(ACE)と呼ぶ。ACLにACEが一つも無ければ、 全てのアクセスは拒否される。 上述の例中で、例えば(5)は、「中居」に対して、「書き込み」アクセスを、「許 可」する、というACEである。すなわち、ACE中には、対象となるユーザまたはグル ープのSID、アクセスの種別、許可か拒否か、という情報が含まれている。 セキュリティ記述子に含まれるACLは、実際には二つある。上記に例示したACLは随 意ACL(DACL)と呼ばれるACLである。DACLは、アクセスの許可・拒否を制御するリス トである。この他に、システムACL(SACL)とよばれるACLが存在する。 セキュリティ記述子には、DACLが含まれていないこともある。この場合は、全ての アクセスが許可されるとみなされる。空のACLの場合と混同しないように注意が必 要である。 プロセスがオブジェクトをアクセスする際には、上記のように自動的にシステムが セキュリティをチェックする。他のプロセスのアクセストークンと特定のオブジェ クトのセキュリティ記述子によってアプリケーションでチェックを行いたい場合は 、AccessCheckを使用する。 ・ACLとACE ACLの情報を取得・設定するにはGetAclInformationとSetAclInformationを使用す る。取得できるのはACEの数、ACL中で使用されているバイト数、未使用バイト数、 リビジョン情報などである。設定できるのはリビジョン情報のみである。 ACE中には、対象となるユーザまたはグループのSID、アクセスの種別、許可か拒否 か、という情報が含まれていると前述した。より具体的には、ACEは以下の情報を 含む。 (1)対象となるSID (2)アクセスの種別 (3)許可、拒否、監査のどれか (4)ACEの属性。例えば、「このACEがディレクトリに対するACEのとき、ディレクト リ内に作られたサブディレクトリがこのACEを継承する」「ディレクトリ内に作ら れたファイルがこのACEを継承する」など。 (5)このACEのサイズ ACL中のACEを取得するには、GetAceを使用する。 ACLが正しいものかどうかを判定するにはIsValidAclを使用する。 DACLを新規に作成するには、InitializeAclで空のACLを作成する。次にAddAccessA llowedAceとAddAccessDeniedAceで、ACEを追加していく。これらの関数は、ACLの 末尾にACEを追加する。任意の位置に追加するにはAddAceを使用する。ACEを削除す るにはDeleteAceを使用する。 ACLにACEを追加する際にはサイズに注意しなければならない。InitializeAclによ るACLの作成時には、ACLのサイズを指定する。従って、このサイズを超えてACEを 追加することはできない。 ACLに、(他のACLから取得するなどして得た)いくつかのACEを複製したい場合、Fin dFirstFreeAceで、ACL中の未使用メモリ領域を取得し、そこへ複製し、また、ACL のACE数の情報を設定する。 SACLの場合は、AddAuditAccessAceでACEを追加する。 ・アクセストークン アクセストークンは、前述の通りプロセスに付加されているセキュリティ情報であ る。アプリケーションは、特別にスレッドごとにアクセストークンを割り当てるこ ともできる。アクセストークンが割り当てられているスレッドからオブジェクトに アクセスしようとした場合、システムは、プロセスのアクセストークンではなく、 そのスレッドのアクセストークンによってセキュリティをチェックする。プロセス に付与されているトークンをオープンするにはOpenProcessToken、スレッドに付与 されているトークンをオープンするにはOpenThreadTokenを使用する。 後述するように、セキュリティ記述子にはオーナーSID、プライマリグループSID、 (前述の)DACLが含まれている。では、あるプロセスがオブジェクトを新規に作成し た場合、このオブジェクトのセキュリティ記述子にはどのような値が格納されてい るのだろうか。実はアクセストークンには、前述の例で挙げたユーザSIDとグルー プSIDの他に、プロセスが作成したオブジェクトのセキュリティ記述子へデフォル トで設定する値についての情報が含まれている。 更に詳述する。SDKのドキュメントによれば、アクセストークンには具体的には以 下のような情報が含まれている。 (1)ユーザのSID。TOKEN_USER構造体で表される。 (2)グループのSID。TOKEN_GROUPSで表される。この構造体には、1個以上のグルー プSIDが格納される。また、それぞれのグループに対する属性も格納される。 (3)特権(後述) (4)オーナーSID。TOKEN_OWNER構造体で表される。プロセスが作成したオブジェク トのセキュリティ記述子のオーナーSIDに設定されるSIDが格納される。 (5)プライマリグループのSID。TOKEN_PRIMARY_GROUP構造体で表される。プロセス が作成したオブジェクトのセキュリティ記述子のプライマリグループSIDに設定さ れるSIDが格納される。 (6)デフォルトのDACL。TOKEN_DEFAULT_DACL構造体で表される。プロセスが作成し たオブジェクトのセキュリティ記述子のDACLに設定されるDACLが格納される。 (7)アクセストークンのソース。TOKEN_SOURCEで表される。このアクセストークン の発行元を表す。 (8)トークンがプライマリトークンか偽装トークンか。TOKEN_TYPE列挙型の値。 (9)現在の偽装レベル。SECURITY_IMPERSONATION_LEVEL列挙型の値。 (10)その他の統計値。TOKEN_STATISTICS構造体で表される。トークンオブジェクト のLUID、このトークンのログオンセッションのLUID、トークンの有効期限、偽装ト ークンかどうか、偽装レベル、トークンが修正されるたびに更新されるLUID、その 他の情報が含まれている。特に、トークンが修正されるたびに更新されるLUIDを参 照することにより、トークンが変更されているかどうかを判定することができる。 これらの情報を取得するにはGetTokenInformationを使用する。オブジェクトの新 規作成時にセキュリティ記述子にデフォルトで与える値についての情報を設定する にはSetTokenInformationを使用する。グループSIDを設定するにはAdjustTOkenGro upsを使用する。 アクセストークンを複製する場合はDuplicateTokenを使用する。 ・セキュリティ記述子 セキュリティ記述子には、前述のDACLとSACLの他に、そのオブジェクトの所有者を 表すオーナーSID、グループを表すプライマリグループSIDなどが含まれている。 DACLを取得・設定にはGetSecurityDescriptorDaclとSetSecurityDescriptorDaclを 使用する。SACLを取得・設定するにはGetSecurityDescriptorSaclとSetSecurityDe scriptorSaclを使用する。オーナーSIDを取得・設定するには、GetSecurityDescri ptorOwnerとSetSecurityDescriptorOwnerを使用する。プライマリグループSIDを取 得・設定するにはGetSecurityDescriptorGroupとSetSecurityDescriptorGroupを使 用する。 セキュリティ記述子を新規に作成したい場合、SECURITY_DESCRIPTOR構造体のバッ ファを用意し、InitializeSecurityDescriptorで初期化した後に、上記設定関数で 各情報を設定していく。用意するバッファのサイズはSECURITY_DESCRIPTOR_MIN_LE NGTHである。 オブジェクトに対して、セキュリティ記述子を取得・設定するには、オブジェクト の種類により、いくつかの関数を使用する。ファイル・メールスロット・パイプに 対する取得・設定は、GetFileSecurityとSetFileSecurityを使用する。カーネルオ ブジェクト(プロセスやスレッドなど)・同期オブジェクト(ミューテックスやセマ フォなど)に対しては、GetKernelObjectSecurityとSetKernelObjectSecurityを使 用する。ウィンドウ管理オブジェクト(デスクトップやウィンドウなど)に対しては 、GetUserObjectSecurityとSetUserObjectSecuritを使用する。レジストリに対し ては、RegGetKeySecurityとRegSetKeySecurityを使用する。サービスに対しては、 QueryServiceObjectSecurityとSetServiceObjectSecurityを使用する。 アプリケーションが固有のオブジェクトをCreatePrivateObjectSecurityやDesktro yPrivateObjectSecurityで作成した場合、セキュリティ記述子の取得・設定はGetP rivateObjectSecurityとSetPrivateObjectSecurityで行う。 セキュリティ記述子が正しいものかどうかを判定するにはIsValidSecurityDescrip torを使用する。 ・セキュリティ記述子の絶対形式と相対形式 例えば、あるアプリケーションが以下の構造体で表されるデータを扱いたいとする 。 typedef struct { char *名前; char *住所; }; このデータは、例えば以下のような形式でメモリ上に格納される。 番地 データ a000 a010番地 a004 a020番地 a010 "広末" a020 "東京" そのアプリケーションが通信を行うアプリケーションで、上記のデータを送信した い場合、単純にa000番地からa020番地までのメモリデータを送信しても、失敗する 。受信側では、以下のように受け取るかもしれないからである。 番地 データ b000 a010番地 b004 a020番地 b010 "広末" b020 "東京" 受信側アプリケーションは、受け取ったデータから、名前を取得しようとしてa010 番地を見るが、そこには名前データは存在していない。 このような場合、ポインタの代わりにオフセット値を使用することが有効である。 番地 データ a000 0x10バイト先 a004 0x1cバイト先 a010 "広末" a020 "東京" 受信側アプリケーションは、受け取ったデータをb000番地からb020番地までのメモ リ領域に格納するとする。a000番地に格納されている、「名前データは0x10バイト 先に格納されている」という情報を読み、b010番地へアクセスし、名前データを取 得することができる。このように、データの存在する場所をメモリアドレス値では なく何バイト離れているかで表す値のことを、オフセット値と呼ぶ。 セキュリティ記述子には、絶対形式と自己相対形式がある。絶対形式はセキュリテ ィ記述子内のデータをポインタで表している。自己相対形式は、セキュリティ記述 子内のデータをオフセット値で表している。セキュリティ記述子を通信上で送信す る場合などは、自己相対形式を用いなければならない。Win32では、これらの形式 間の変換のための関数を用意している。絶対形式のセキュリティ記述子を自己相対 形式のセキュリティ記述子に変換するには、MakeSelfRelativeSDを使用する。逆の 変換には、MakeAbsoluteSDを使用する。なお、通常システムが作成するセキュリテ ィ記述子は、自己相対形式である。 ・セキュリティ記述子の制御情報 セキュリティ記述子は、DACLやSACLが存在するかどうか、絶対形式か自己相対形式 かなどの情報を取得することができる。このような情報を取得するにはGetSecurit yDescriptorControlを使用する。 ・アクセス権 前述の例では、「書き込み」「読み込み」などのアクセス権の例を挙げた。アクセ ス権は、32ビット値で表され、その値の各ビットがそれぞれ別のアクセス権を意味 している。 アクセス権には、標準アクセス権、固有アクセス権、汎用アクセス権の三つの種類 がある。 標準権利は、どんなオブジェクトにも共通で定義されているアクセス権である。標 準アクセス権の主なものとしては、「削除アクセス権」「読み込みアクセス権」「 書き込みアクセス権」「実行アクセス権」「同期アクセス権」「DACLの書き込みア クセス権」などがある。全てのオブジェクトに対して、ACE中で、これらのアクセ ス権を用いることができる。 固有アクセス権は、特定の種類のオブジェクトのみに定義されているアクセス権で ある。例えば、レジストリオブジェクトには「サブキーを作成する権利」「値の作 成・設定する権利」「サブキーを列挙する権利」などの固有アクセス権がある。ま た、名前付きパイプオブジェクトには、「サーバへの送信アクセス権」「サーバか らの読み取りアクセス権」などの固有アクセス権がある。これらのアクセス権は、 対象となるオブジェクトでのみ有効なアクセス権である。 今、レジストリにデータを設定したいとすると、レジストリオブジェクトに対して 、標準アクセス権の「書き込みアクセス権」と、固有アクセス権の「値を作成・設 定する権利」「サブキーを作成する権利」の、合計三つのアクセス権を、オープン 時に要求することになる。このように、あるオブジェクトに対して読み込みや書き 込みが行いたいときに、オブジェクトの種類ごとにどのようなアクセス権を設定す れば良いのかをインプリメントするのは複雑である。 Win32では、これに対して汎用アクセス権というものを定義している。汎用アクセ ス権には「読み込みアクセス権」「書き込みアクセス権」「実行アクセス権」など がある。Win32は、汎用アクセス権を、そのオブジェクトに対する標準アクセス権 と固有アクセス権に翻訳する、MapGenericMaskという関数を用意している。従って 、例えば任意のオブジェクトに対して「読み込み」を行いたいときは、汎用アクセ ス権の「読み込みアクセス権」を、MapGenericMaskで、そのオブジェクトの標準ア クセス権と固有アクセス権に翻訳し、オープンを行えばよい。 前述の通り、アクセス権は32ビットの値で表される。32ビット中の下位16ビットは 固有アクセス権を表すビットである。32ビット中の16ビットから23ビットの間のビ ットは、標準アクセス権を表すビットである。(なお、汎用アクセス権は28ビット から31ビットの間のビットが使用される) アクセス権のチェックをアプリケーションが行いたい場合、AreAllAccessesGrante dやAreAnyAccessesGrantedを使用する。前者は、要求されたアクセスが全て満たさ れているかどうかをチェックし、後者は、要求されたアクセスのうち1つ以上が満 たされているかどうかをチェックする。 ・偽装 ・特権 ある特殊な操作に対する許可・拒否の情報は、セキュリティ記述子には含まれてい ない。このような操作の例としては、システムのシャットダウンや時刻の変更など が挙げられる。このような特殊な操作を行う権利を特権と呼ぶ。アクセストークン には、プロセスが持っている特権情報が含まれている。 特権はアクセストークン内ではLUIDで表されるため、アプリケーションがアクセス トークンに特権を設定したい場合は、その特権のLUIDを求める必要がある。Lookup PrivilegeValueは、特権をユニークに識別する、システム共通の文字列から、その 特権のLUIDを取得する。この文字列は、例えばシャットダウン特権ならば、"SeShu tdownPrivilege"などである。逆の変換は、LoopupPrivilegeNameを使用する。 "SeShutdownPrivilege"などの特権を表す文字列ををユーザに理解可能な文字列に 変換するには、LookupPrivilegeDisplayNameを使用する。 ユーザマネージャなどによって、ユーザやグループに対して許される特権を設定す ることができる。しかし、初期状態のプロセスには通常それらの特権は与えられて いない。プロセスがシャットダウンなどを行いたい場合、まずAdjustTokenPrivile gesによって、特権を得る。プロセスを作成したユーザやグループが、その特権を 得ることを許されていたら、特権を得るのに成功する。その後に、プロセスはExit WindowsExなどでシャットダウンを行う。 先に述べたように、アクセストークンはプロセスにもスレッドにも付加させること ができる。システムが特権をチェックする場合、呼び出し元スレッドがアクセスト ークンを持っていれば、そのアクセストークン内の特権情報を判定する。しかし、 SE_TCB_NAME特権だけは例外で、呼び出し元スレッドがアクセストークンを持って いたとしても、システムはこの特権を持っているかどうかの判定を、スレッドのア クセストークンではなく、プロセスのアクセストークンに対して行う。