10. 存取控制

 
Microsoft Windows 2000提供了广泛且具高度弹性的安全功能,在现今市场上没有其他的作业系统能在安全对象上提供细微的控制。而Windows实作了 存取控制 部份,所以可以达到如此重要的程度。

存取控制简介
 

一般来说,当人们提到「Windows安全性」,即是指实作Windows的 存取控制 部份。存取控制可以被解释为指派及强制可以或不可以在安全对象上执行某些动作。Windows把存取控制用在一些系统对象上,并提供自订对象的安全机制。

安全对象
 

系统将安全性加到一些对象及系统提供的特色上。透过存取控制而取得安全性的任何对象皆被视为安全对象。表10-1显示出本书编写时,Windows 2000提供的安全对象类型(系统也提供一个称为SE_OBJECT_TYPE的列举类型,它包括每个类型的值)。如您所见,Windows中有许多主要元件可以利用存取控制功能,包括自订及私人对象的部份。

Windows 2000可让您的服务软件将应用程序定义的任何对象类型遵循Windows的存取控制。系统会管理对象安全的部份,而您的软件则结合安全与对象,并管理对象本身的功能。这种安全的管理称为 私人对象安全 ,本章稍后将会作详细的讨论。

 表10-1 Windows 2000中的安全对象类型
种类 SE_OBJECT_TYPE列举类型 叙述
文件对象SE_FILE_OBJECT在NTFS文件系统上的文件或目录。
服务对象SE_SERVICE系统上安装的服务或系统的服务控 制管理员(Service Control Manager ,SCM)。
打印机对象SE_PRINTER一个打印机或列印服务器。
登录机码SE_REGISTRY_KEYWindows 2000系统上的登录机码。
分享对象SE_LMSHARE指出Windows 2000系统上的分享共 享目录对象。
核心对象SE_KERNEL_OBJECT系统可以保护以下所列之核心对象 安全:程序对象、线程对象、工 作对象、号志(Semaphore)对象、 事件对象、Mutex对象、文件对应 对象、可等待的计时对象、存取权 杖(Token)、命名管道及匿名管道。
Windows对象SE_WINDOW_OBJECTWindow站台及桌面对象(本章稍后会加以叙述)。
目录服务对象SE_DS_OBJECT SE_DS_OBJECT_ALLWindows 2000可让您将安全性运用在Active Directory的对象或目录服务上。
WMI对象SE_WMIGUID_OBJECT揭露给WMI的对象。
安全提供者对象SE_PROVIDER_DEFINED_OBJECTWindows 2000支援可置换的安全提供者,而且可以显示安全对象。
私人对象 由服务或系统保护之应用程序建立的自订对象。

Windows之存取控制的长处之一是确保对象类型间执行的主要对象安全程序是相同的。这种通用的设计使得管理者及程序设计师在保护不同对象的安全时,变得更为简单。每个安全对象皆保存着一个 存取控制清单 (Access Control List,ACL),用来决定谁可以或不可以在对象上执行某些安全动作。这个ACL是Windows 2000存取控制的核心,以下列叁种方式的组合与安全对象结合:

  •  预设指派(Default assignment) Windows 2000提供一个弹性的预设安全结构,指派存取控制到没有明确经由新建软件取得安全性的对象。这个机制是最常用来指派安全性给对象的方式,也是所有忽视Windows 2000安全性软件使用的机制。对象被建立时,会指派预设的安全给对象。
     
  •  继承(Inheritance) 存在父系—子系关系层级的系统及私人对象(例如文件及登录机码)可能也会受安全继承的管制。继承可让适用于父对象的安全传播到层级中的子对象。假如继承的存取控制项目(Access Control Entries,ACEs)是可用的,则它们适用新的对象,而非预设的安全。不管是否有指派外显的存取控制清单,继承皆被应用在对象上。
     
  •  外显指派(Explicit assignment) 您可以在对象建立时或建立后,明确地指派存取控制到安全对象。这种指派安全的方式在服务软件中比在应用程序及客户端软件中更常见。
     

建立及指派ACLs给安全对象为本章首要的主题。

存取权利概述
 

尽管我们已经解释对象具有安全性,但还未讨论到安全对象的实际意义。Windows对安全对象上的可执行动作提供了一个很好的控制等级。只有在提出要求的信任成员拥有对对象的适当存取权利时,该安全的动作才会被执行。表10-2叙述了叁种可运用在对象上的存取权利群组。

 表10-2 系统定义应用于对象上的存取权利
存取类型 叙述
标准权利标准权利应用于系统的所有对象,包括删除对象或读取对象等安全性的权利。有关Windows 2000定义的所有标准权利清单,请参阅 
表10-13 的内容。
特殊权利特殊权利只应用在特殊类型的安全对象上。举例来说,某个文件对象的特殊权利可能会是新增资料或从文件中读取资料的权利。
通用权利通用权利指某个对象的标准及特殊权利的集合。系统定义了四个通用的权利:读取、写入、执行及所有。每个通用权利的意义与对象对对象的情形不同。

我会详细讨论每种权利的类型。而此时先把权利看成允许或不允许使用者对对象作某些事是重要的。例如,一个文件可让使用者读取,但不让使用者删除:这是有可能的,因为系统为读取及删除定义了不同的权利。

当使用者企图在对象上执行安全性工作时,系统会执行 存取检查 的动作。存取检查会搜寻被指派给对象的权利,并与尝试动作的使用者身分作对照。假如系统判定使用者已经要求存取对象,则动作被执行。若使用者没有适当的存取,则该动作不会被执行,代表使用者执行的软件会接收到「拒绝存取」的错误讯息。


说明

系统也提供回报尝试存取安全对象结果(成功及不成功)的能力。称为稽核。稽核事件被记录到事件日志中。任何被允许或拒绝给予对象存取权利的事件也会被稽核。尽管稽核与存取控制的情形相似,但它是单独且不常使用的特色。大部份您所学到关于存取控制的部份皆会应用到稽核,所以最好现在就了解其功能。(本章稍后的〈稽核及SACL〉一节中将讨论稽核的内容)。


安全描述项(Security Descriptor)
 

如前所述,系统中每个安全对象类型的安全使用任何与其他安全对象相同的方式大量被储存及运用。这是因为在Windows 2000中的所有存取控制皆使用一个称为 安全描述项 的资料结构实作。 安全描述项 维护了一个对象的重要安全资讯,例如它的拥有者,以及和系统使用者相关的权利清单。每个在Windows中的安全对象都有一个安全描述项。图10-1显示安全描述项的样子,表10-3则叙述了元件的内容。您将会发现您常使用到与拥有者及安全描述项之判别存取控制清单(Discretionary Access Control List,DACL)元件相关的部份。


 

 图10-1 安全描述项

之前曾经讨论过,从一个Windows中的对象安全类型到另一个类型的操作,有很多相同的程序。典型的情形下,安全描述项经由您的软件产生,然后传递到建立对象的系统函数中,并分派安全给对象。

 表10-3 安全描述项的元件
元件 叙述
Revision指出安全描述项结构之修订等级值。
Control指出安全描述项之内容意义的一组标记。
Owner对象拥有者的安全识别码(SID)。对象拥有者有特殊的权利-即拥有者可以一直读取及修改对象的安全性,不管是否有明确指派DACL中叙述的权利。所有安全对象都有一位拥有者(关于SIDs的讨论,请参阅 第九章 )。
Group主要群组对象的SID。Windows 2000在存取控制上没有利用到主要的群组。因为拥有维护资讯,使得Microsoft Windows NT可被用来当作操作系统的文件服务器平台。
DACL判别存取控制清单(Discretionary Access Control List,DACL)是叙述对象所有存取权利的存取控制清单。这个清单定义了谁可以或不可以在对象上执行安全动作。假如没有目前的DACL,则每个人都拥有对该对象的所有存取权利。假如当前的DACL是空的,则除了拥有者外,没有人拥有对该对象的存取权利。
SACL系统存取控制清单(Sysem Access Control List,SACL)是与以稽核为目的使用者相关的存取权利清单。SACL不会影响对对象的存取情形,但它会将对对象的存取记录到事件日志中。有关此部份的更多资讯,请参阅本章稍后的〈 稽核及SACL 〉一节。

您也可以撷取现有安全描述项对象的副本,使用系统函数读取或修改安全描述项,然后再使用系统函数设定安全描述项的原始对象。


说明

一旦您从对象撷取一份副本后,使用系统函数修改安全描述项的方式似乎有效率偏低的情形。您可以想想为何不用您的软件直接存取资料的方法-答案是,在您的处理程序内存中已有一份资料结构的副本。尽管您可以用这种方法存取资料,但最好不要,因为安全描述项预期会被视为一个「不透明」结构而存取,以使您的程序代码在未来的Windows版本上仍然能正确地运作。


ACLs、ACEs及DACL
 

ACL是多个连续的可变长度结构清单,称为 存取控制项目(Access Control Entries) ,或ACEs。每个ACE皆指出与对象相关之存取权利及信任成员的SID。

DACL及SACL皆是ACLs。DACL被用在对象的存取控制上,它的ACEs内容指出谁被允许或不被允许存取对象。SACL则用在存取稽核的部份。DACL及SACL的结构相同,而且操作它们的程序代码也以同样的方式实作。我将在本章稍后的〈稽核及SACL〉一节中讨论SACL,此处的许多讨论则着重在应用SACL的DACL部份。

当应用程序企图存取安全对象时,系统浏览对象的DACL会寻找ACEs,并指出在应用程序执行之下的使用者信任成员帐户或使用者为其成员的群组信任成员帐户。假如找到符合的ACE,系统便会检查ACE授予或否决应用程序要求执行的存取权利。稍后会对存取检查的部份做更详细的讨论。首先让我们看表10-4中叙述的六种ACE类型。其中,允许存取及拒绝存取的ACE显然是最常用的。


说明

除了目录服务对象之外,Object-type ACEs不与任何系统中的安全对象一起使用。然而,object-type ACEs可以与您自己的私人对象一起使用。此处主要集中在一般的ACE类型上,但本章中仍会对object-type ACEs做简短的讨论。


 表10-4 Windows 2000中的ACE类型
ACE类型(系统使用的值) 叙述
允许存取(ACCESS_ALLOWED_ACE_TYPE)定义一组允许指定信任成员帐户的存取权利
拒绝存取(ACCESS_DENIED_ACE_TYPE定义一组拒绝给指定信任成员帐户的存 )取权利
系统稽核(SYSTEM_AUDIT_ACE_TYPE)定义一个假如经由指定的信任成员帐户执行时,将产生稽核报告的安全动作
允许存取的对象(ACCESS_ALLOWED_OBJECT_ACE_TYPE)定义一个允许对象、对象的子对象或属性之指定信任成员帐户的个别存取权利 (通常与目录服务对象一起使用)
拒绝存取的对象(ACCESS_DENIED_OBJECT_ACE_TYPE)定义一个拒绝给予对象、对象的子对象或属性之指定信任成员帐户的个别存取权利(通常与目录服务对象一起使用)
系统稽核对象(SYSTEM_AUDIT_OBJECT_ACE_TYPE)定义一个假如经由对象、对象的子对象或属性之指定信任成员帐户执行时,产生稽核报告的个别存取权利(通常与目录服务对象一起使用)

允许及拒绝存取之标准ACEs相当简单。表10-5显示了标准ACE的内容。

 表10-5 标准(非对象)ACE的内容
ACE元件 叙述
ACE类型一个以数值表示的值,指出ACE的类型(表10-4显示了Windows 2000中的ACE类型)。
ACE标记指出ACEs的继承规则与SACL之ACEs的稽核规则(有关可用的继承标记清单,请参阅 表10-11 )。
存取遮罩一个32位元的值,指出ACE叙述的存取权利。
信任成员的SID指出与ACE的存取权利相关的信任成员使用者、群组或电脑帐户。

您在本章稍早已学习到系统定义的叁种存取权利类型:即标准、特殊及通用权利。属于ACE一部份的存取遮罩是个32位元的值,每个对象的可能存取权利都与一个位元相对应。


说明

因为每个存取权利皆对应到ACE存取对应中的单一位元,所以一个个别的ACE可被用来指出信任成员帐户的多重存取权利。


存取遮罩被每个Windows系统支援的叁种存取类型划分成数个区段。图10-2即显示了这些位元及其用途。


 
表10-13 )。同样地,如果请求ACCESS_SYSTEM_SECURITY的话,您必须持有SeAuditPrivilege权限(有关权限的讨论请参阅 第九章 )。


说明

您可以建立一个受额外存取权利限制的权杖,并任意地选择信任成员帐户。称为 受限权杖 。假如您的处理程序或线程与受限权杖相关,则可以改变存取检查的规则。这个主题的详细描述涵盖在第十一章。


此处理程序最重要的步骤是步骤四、步骤六、步骤八及步骤九。在步骤四中,假如系统发现对象的安全描述项不包括DACL,则每个人的存取检查成功。在步骤六中,假如拒绝存取的ACE与您的使用者或群组SIDs及存取检查的任一个存取权利相符合,则存取检查会立即失败,不管DACL以后的ACE是否已经通过存取检查。步骤八在所有要求的权利找到后,会通过存取检查,不管DACL以后的拒绝存取ACE是否已经使存取检查失败。最后,在步骤九中,若ACEs的数量不足以通过检查,则表示绝对失败。

如您所见的,在存取检查中,DACL中的ACEs顺序是非常重要的。您应该将DACL中拒绝存取的ACEs放置在允许存取的ACEs之前。Windows 2000的使用者介面实作了ACE的安排功能;然而,在您自己的软件中,可以使用任何的顺序放置ACEs。若在对象DACL中,您把拒绝存取的ACE放置在允许存取的ACE之后,则表示您有如此做的充分理由才会如此做!


说明

DACL末端的拒绝存取ACE是浪费的。假如ACE拒绝已经允许的存取动作,则存取检查连末端的拒绝ACE也不会看。而如果ACE拒绝没有明确的允许存取,那么一开始就拒绝存取是没有必要的-除非它被明确地允许,否则即表示它暗示地拒绝该存取动作。


Microsoft已经发表DACL中的ACEs惯用顺序,称为「惯用的」顺序,因为它不完全是强制执行的。表10-6显示了DACL中ACEs惯用的顺序。

 表10-6 DACL中ACEs惯用的顺序
ACE类型 群组
拒绝存取的ACEs
拒绝存取对象的ACEs,适用于子对象或对象的属性
允许存取的ACEs
允许存取对象的ACEs,适用于子对象或对象的属性
明确地指派(非继承)ACEs
拒绝存取的ACEs
拒绝存取对象的ACEs,适用于子对象或对象的属性
允许存取的ACEs
允许存取对象的ACEs,适用于子对象或对象的属性
继承ACEs

表10-6中的顺序规则看起来可能有点复杂,但请记得,系统中除了目录服务对象(Active Directory中的对象)外,并非所有的安全对象都使用对象ACEs。忽视对象ACEs将会大大地简化ACEs的顺序。

认识自订或私人对象安全性
 

我们已经讨论过系统安全对象、保护对象安全的安全描述项结构,以及系统如何使用DACL检查安全性,以防备软件的请求。然而,我还未说明如何让软件使用Windows安全模组建立安全对象。Windows提出此功能为 安全私人对象 

私人对象安全性是包含在Windows中的一个非常强大且具有弹性的特色。在本章的后续部分,将会讨论与私人对象一起使用的安全性API。此时,要先说明如何将私人对象安全性与Windows现有的安全模组结合。

除了您的软件外,您已学习之有关安全描述项、ACLs、DACLs及ACEs的每件事皆适用于私人对象上,并非系统,您必须决定哪个标准权利(列于表10-13)适用于您的对象。此外,您必须为您的对象定义特殊的权利,并将四个通用权利对应到适当标准及特殊权利的结合上。

您的软件经由呼叫系统函数来执行存取检查。通常,您的软件是个服务,它传递相关的客户端权杖到安全描述项的系统中。然后系统会指出是否拥有客户端要求的存取权利。根据存取检查的结果,您的服务应对执行或拒绝执行要求的动作负责。

系统为您的私人对象建立及删除内存中的安全描述项。您必须将安全描述项与他们保护的资料联系在一起。当服务结束时,它也应负责储存安全性与资料到永续性储存体(Persistent Storage)上(假设对象为持续的情形)。


说明

私人对象安全性不会自动地保护软件定义的对象资料安全性-假如您的对象储存在文件中,您还必须保护文件的安全。然而,私人对象安全性提供可让您以更细微的层级控制资料的机制,而不须受限于文件安全或Windows中其他储存机制的安全性。


浏览Windows的安全性
 

为了成为一个成功的安全性开发人员,了解及熟悉Windows 2000安全性是很重要的。花些时间在利用使用者介面修改系统对象的安全性上,将大大地使您增加有效地设计安全性软件的能力。

以下的章节将带领您贯穿Windows提供的工具,并帮助您熟悉安全性的内容。在Windows中,可使用登录机码、文件及目录(只在NTFS分割上)取得安全性。文件系统可能是熟悉安全性的最有效方法,因为它的继承模组包含容器对象(目录)及非容器对象(文件)。假如您没有可用NTFS分割区,则可以使用登录。

登录的存取控制
 

这些步骤叙述如何将安全性选项指定到登录中的方法:

  1. 以管理员或管理员群组的成员身分登录您的系统。
  2. 执行RegEdt32.exe公用程序,您将看到类似图10-4的画面。
     

     图10-4 登录编辑器(RegEdt32.exe)

     

     图10-5 为ANewKey设定继承的权限
  3. 选择标题为本机上的HKEY_LOCAL_MACHINE视窗,此视窗会显示您系统上储存的HKEY_LOCAL_MACHINE内容。
  4. 开启Software机码并从编辑功能表中选择新增机码选项,新增一个称为ANewKey的新机码(新机码将命名为ANewKey,以使它显示在接近Software下的机码清单顶端)。
  5. 点选新的机码,并从安全性功能表中选择使用权限选项,此时会出现一个权限对话方块。
  6. 取消核取允许来自父项的可继承权限传播至此对象的核取方块,如此会使您的新机码安全描述项被保护,不允许从父机码传播可继承权限。系统会询问您是否希望复制或移除目前继承的权限,如图10-5所示。
  7. 点选移除按钮以移除存取清单。
  8. 现在新机码的DACL内容是空的。假如您点选对话方块中的确定钮,则除了您之外(因为您是此机码的拥有者),没有人可以对这个机码作任何事。而您唯一可作的事,将是读取或写入对象安全性。
  9. 点选新增钮以显示选择使用者、电脑或群组对话方块内容。从清单中选择Everyone,点选新增钮,然后按下确定按键。这表示为Everyone新增一个ACE到您机码的DACL中。
  10. 点选允许之下的完全控制核取方块,并点选确定按钮,登录编辑器会为您的机码建立一个新的安全描述项,以让Everyone完全控制这个机码。
  11. 选择ANewKey然后从编辑功能表中选择新增机码选项,对ANewKey新增两个子机码。(我将我的新机码取名为First及Second,但您可以用任何您喜欢的名字命名。)
  12. 登出您的机器,以内建的Guest帐户登入(您可能必须赋予这个帐户使用能力),然后重新执行RegEdt32.exe。除了您自己这部分外,可以使用任何信任成员帐户(另一个登出的选择是使用RunAs公用程序,以Guest帐户的身分启动RegEdt32.exe。例如:RunAs.exe /env /user:mymachine/Guest RegEdt32.exe)。
  13. 在HKEY_LOCAL_MACHINE/Software下找出您的新机码并且开启它,您应该会看到新的子机码。点选它然后从安全性功能表中选择使用权限选项,检查这两个子机码的权限,注意到这两个机码都已经从父机码继承了简单的Everyone/ 完全控制的安全性,此时不要核取允许继承权限核取方块。
  14. 由于是内建的Everyone群组成员的缘故,否则不会赋予Guest帐户可对这些机码作任何它想作的事,包括改变它们的权限。所以利用这个权力并且开启其中一个子机码的权限。
  15. 核取拒绝下方的完全控制核取方块,以拒绝对Everyone群组的完全控制。还不须点选确定钮。
  16. 您正在做的事是为子机码新增拒绝存取的ACE到DACL中。然而,请注意,允许下方的核取方块仍旧是被核取的。这是因为继承允许对Everyone完全控制的允许控制ACE仍出现在当前的DACL中。然而,您明确要求的ACE将优先于任何继承的ACEs。现在点选确定钮,然后在询问您是否想要继续的安全性对话方块中点选是。
  17. 现在您已经实际地拒绝每个人(包括您登录的这个帐户)对这个机码的任何存取权限。没有人可以在这个机码下建立子机码或值。只有拥有者(应该是您经常登录的帐户)可以修改允许权限的安全性。
  18. 在您登出并且以您标准的帐户重新登录之前,试着编辑刚修改的登录机码权限。系统应该会告诉您没有足够的存取权利去检视或编辑这个机码的安全性(然后系统将显示一个空的安全描述项,以防止您有存取这个机码的写入权利,而您确实没有这个权力)。

对NTFS分割区的存取控制
 

以下是说明NTFS分割上的文件权限运作范例:

  1. 为了检视文件的权限,在Windows浏览器中的文件或资料夹上点选右键,然后从环境功能表中选择属性,以显示属性对话方块,选择安全性页签以显示文件的权限。设定文件权限大部份是以同样的方法,也就是RegEdt32中的登录机码。
  2. 在属性对话方块中,点选进阶钮以显示存取控制设定对话方块,如图10-6所示。新增非继承的存取权限,或新增只继承到非容器(或文件)的存取权利。
     

     图10-6 设定NTFS分割上的文件权限
  3. 在属性对话方块中,点选进阶钮。假如您是拥有者,则应该能够将自己或您的其中一个群组设定为拥有者。此外,如果您的帐户拥有SeTakeOwnership Privilege权限,则您应该能设定系统中任何对象的拥有者。要查看拥有者清单,请点选拥有者页签。
  4. 在浏览器中,建立几个目录层级,上层是您用受保护的安全描述项所建立的新目录,然后再新增足够的存取权利以继续建立目录。接着,经由新增更多允许存取或拒绝存取的ACEs来改变阶层中间目录的安全性。现在请查看阶层底端的目录或文件,并且注意它如何从多重父系中继承权限。请试着从阶层中的父系移除一些权限并注意它如何影响子系或Grandchild。您也可以使用登录机码的阶层做这类的测试。

此处强烈地建议您花更多时间来试验登录机码或文件的权限(假如您有NTFS分割)。浏览这些内容将有助于您具有成为一位安全性程序设计师的能力。

存取控制术语复习
 

假如您对Windows的安全性程序设计不熟悉,刚才已经连续向您提出了几个新术语及观念。所以在我们潜心研究安全性API的奥秘前,正好可以重新复习一下我们已经学习的部分。

  •  存取检查 由系统(或您私人对象的软件)执行的检查,用来决定由权杖识别的使用者是否拥有某个对象之DACL允许的存取权利组。
     
  •  存取控制 系统为了对对象安全性存取之管理及实施所提供的特色。
     
  •  存取遮罩 ACE内部的32位元值,为对象的每个存取权利包含了一个位元,包括标准、特殊及通用权利。
     
  •  存取权利 系统或软件定义的值,指出在安全对象上或与安全对象一起执行某些动作所须的权利。
     
  •  ACE 代表存取控制项目。ACE包含了一个识别系统信任成员的SID,以及指出存取权利的存取遮罩。ACE可以允许或拒绝存取某个对象。
     
  •  ACL 代表存取控制清单。ACL包含了定义安全性的ACEs或某个对象的安全性报告。
     
  •  DACL 代表判别存取控制清单。DACL包含了明确地允许及拒绝存取某个对象的ACEs。
     
  •  通用存取权利 一般的「读取」、「写入」或「执行」某个对象的权利,以及通用的「所有」存取权利。每个通用权利皆对应到一组标准及特殊权利;这些对应会因为对象的每个类型而不同。
     
  •  保护的安全描述项 一个具有控制标记的安全描述项,指出它和它的子系不该从它的父对象接受可继承的ACEs。
     
  •  SACL 代表系统存取控制清单。SACL包含了ACEs为相关的使用者指定应被记录到事件日志的事件。
     
  •  安全的对象 任何被Windows存取控制模组保护的私人或系统对象。
     
  •  安全描述项 与系统中每个安全对象关联的结构。安全描述项包括指出拥有者及主要群组的SID,以及非必需的DACL及SACL。
     
  •  SID 代表向系统识别信任成员帐户的安全识别项。第九章中有SIDs的详细讨论内容。
     
  •  特殊的存取权利 由系统或软件定义只适用于特定的系统类型或私人安全对象的存取权利。
     
  •  标准的存取权利 由系统定义的存取权利,其子集合将被应用到系统中的每个安全对象类型上。
     
  •  权杖 一个与程序或线程相关联的结构,包含识别使用者及使用者群组的SIDs与使用者持有的权限。第十一章会对权杖做详细的讨论。
     
  •  使用者内容 假如软件在您的权杖下执行,那就是表示在您的使用者环境中执行。
     

存取控制的程序设计
 

在这一节中,我们将讨论如何有计划地操作Windows中的安全性,我们将从相关步骤的概念开始谈起。

安全性任务的基本步骤
 

不管您处理的对象类型为何,其修改安全对象之安全性方法大致相同。您通常会执行两个工作中的其中一个:即建立一个具有安全性的新对象,或者变更现有对象的安全性。以下是您在建立具有安全性对象时将执行的基本步骤:

  1. 编辑SIDs清单,您将为SIDs建立拒绝及允许的ACEs。
  2. 建立及初始化安全描述项。
  3. 建立及初始化够大的DACL,以保存所要求的ACEs。
  4. 新增ACEs到DACL。
  5. 新增DACL到安全描述项。
  6. 使用新的安全描述项建立对象。
  7. 依照您所需的情形整理。

从一个安全对象类型到下一个类型的操作时,只有步骤六会不同。若要修改现有对象的安全性,请按照以下这些步骤执行:

  1. 编辑SIDs清单,您将为SIDs建立拒绝及允许的ACEs到对象的DACL。
  2. 取得对象的DACL。
  3. 为您想要移除的ACEs检查现有的DACL,然后移除它们。
  4. 为您新增的ACEs检查现有的DACL,以便您可以大量地避免再次新增它们及不必要的建立。
  5. 建立一个够大的DACL,以容纳除了新的ACEs外,还有修改过的「旧的」DACL。
  6. 复制旧的ACEs及新增的ACEs到「新的」DACL。
  7. 设定DACL给对象。
  8. 依照您所需的情形整理。

在这个处理程序中,从一个安全对象类型到下一个类型的操作,只有步骤二(取得DACL)及步骤七(请求DACL)会有所不同。

不要使自己被这些处理程序压垮。我将详细地讨论每个步骤,并且叙述选择的几个步骤。在此处提及这些程序的目的,是为Windows中的 任何一个 安全对象显示一般的方法。

如您所见,一旦熟悉了修改对象安全性的方法后,就会拥有修改任何对象安全性所需的技术(甚至是程序代码)。修改方法中的主要差别在于安全性的取得及设定部份。如果您已经知道与您相关的对象类型应使用哪个函数,则您已经准备好了。请看表10-7。


 
表10-13 )。

如果这些条件都不为真,则您没有读取对象安全性的权利。若任何一个为真,当您取得对象的handle时,可以在用来取得handle的函数存取要求参数中,要求READ_CONTROL存取,系统将会给您一个handle。以下的程序代码片段显示为了读取文件安全性资讯而取得handle的范例:

HANDLE hFile = CreateFile(TEXT("C://Test//Test.txt"), READ_CONTROL, 0, 
	NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if ((hFile == INVALID_HANDLE_VALUE) &&
	GetLastError() == ERROR_ACCESS_DENIED){
	// 您没有对这个文件的READ_CONTROL存取权限
}

如果这个C:/Test/Test.txt文件存在并常驻在NTFS磁盘上,则此程序代码片段将会产生作用。若您符合了两个条件之一,则此程序代码将会使您取得一个有效的文件handle,您可以用它来读取对象的安全性。

现在您拥有了某个安全对象的handle,例如文件,让我们经由呼叫GetSecurityInfo函数,并使用此handle去撷取对象的安全性资讯:

DWORD GetSecurityInfo(
	HANDLE	hHandle,
	SE_OBJECT_TYPE	objType,
	SECURITY_INFORMATION	secInfo,
	PSID	*ppsidOwner,
	PSID	*ppsidGroup,
	PACL	*ppDACL,
	PACL	*ppSACL,
	PSECURITY_DESCRIPTOR	*ppSecurityDescriptor);

说明

您可能会从表10-7中想到GetSecurityInfo函数是被用来为Windows的大多数安全对象撷取安全性资讯。它确实是个非常有弹性且有用的函数。


使用GetSecurityInfo时,您会传递一个handle到已经以READ_CONTROL存取并开启的对象中,以视为hHandle参数之内容。objType参数是个列举的型态,指出handle代表的安全对象类型。表10-1显示可与此函数一起被使用的不同列举值。举例来说,假如您传递了文件的handle为GetSecurityInfo的hHandle参数,您必须传递此列举值SE_FILE_OBJECT为objType参数。

secInfo参数则指出在对象的安全描述项中,您要系统传回的资讯。还记得一个对象的安全描述项保存了一个拥有者、群组、DACL及SACL,经由传递表10-8中的任意组合值,可以使用GetSecurityInfo以撷取任何或所有这些资讯。

 表10-8 可被传递给GetSecurityInfo之secInfo参数SECURITY_INFORMATION值
叙述
DACL_SECURITY_INFORMATION指出您想要撷取安全对象的DACL资讯
SACL_SECURITY_INFORMATION指出您想要撷取安全对象的SACL资讯
OWNER_SECURITY_INFORMATION指出您想要撷取安全对象的拥有者资讯
GROUP_SECURITY_INFORMATION指出您想要撷取安全对象的群组资讯

ppsidOwner、ppsidGroup、ppDACL及ppSACL参数是选择性的。假如您要系统传回指向拥有者的SID、群组SID、DACL或对象的SACL指标,可以传递PSID的位址或者PACL变数给任何或所有这些参数。然而,您必须传递NULL给未经传递适当的标记为secInfo参数所要求的任何资讯。无论如何,您可以传递NULL给任何或所有这些参数。

最后一个参数是指向SECURITY_DESCRIPTOR结构的指标位址,它是强制性的。系统使用LocalAlloc分派一个够大的缓冲器以持有对这个对象要求的安全性资讯,并且放置一个指标指向变数中的安全描述项,其位址在ppSecurityDescriptor中提供(当您结束对象的安全描述项时,传递ppSecurityDescriptor参数中被传回的值到LocalFree是必要的)。


说明

假如您应该传递PSID的位址或PACL变数为ppsidOwner、ppsidGroup、ppDACL或ppSACL参数,系统会传回指向ppSecurityDescriptor中传回的部分安全描述项位址。这就是为什么这些参数是选择性的原因。


您不用在呼叫GetSecurityInfo后又呼叫GetLastError,因为GetSecurityInfo会直接传回错误程序代码。假如GetSecurityInfo执行成功,它会传回ERROR_SUCCESS。常见的错误值是ERROR_ACCESS_DENIED,指出您的handle不是以READ_CONTROL存取开启,而ERROR_INVALID_HANDLE则指出您传递了不符合的handle及对象类型值给hHandle及objType参数。

以下的程序代码可以与先前的程序代码片段一起使用(请参阅 先前程序代码片段 ),它使用CreateFile开启文件的handle、撷取DACL及文件C:/Test/Test.txt的对象拥有者资讯。

PSECURITY_DESCRIPTOR pSD; 
PSID pSID;
PACL pDACL;
ULONG lErr = GetSecurityInfo(hFile, SE_FILE_OBJECT,
	DACL_SECURITY_INFORMATION, &pSID, NULL, &pDACL, NULL, &pSD);
if (lErr != ERROR_SUCCESS){
	// 错误实例
}

// 在这执行工作
// 清理
LocalFree(pSD);
CloseHandle(hFile);

在这个范例中,成功地呼叫GetSecurityInfo后,pSID及pDACL变数会指向由pSD变数传回之缓冲器所包含的SID及DACL结构。传递由pSD或pDACL中传回的指标到LocalFree是不必要且不正确的,因为当pSD变数传回的指标被传递到LocalFree时,整个安全描述项将被释放。

您已从第九章中学习了SIDs及指向SIDs的指标,并与所讨论到的LookupAccountSid及CopySid函数一起使用从GetSecurityInfo传回的拥有者SID。

在仔细分析安全对象的DACL前,我想要先介绍GetSecurityInfo的姊妹函数—GetNamedSecurityInfo。GetSecurityInfo要求一个安全对象的handle,而GetNamedSecurityInfo则要求系统之安全对象的文字名称,其定义如下:

DWORD GetNamedSecurityInfo(
	LPTSTR	pObjectName,
	SE_OBJECT_TYPE	objType,
	SECURITY_INFORMATION	secInfo,
	PSID	*ppsidOwner,
	PSID	*ppsidGroup,
	PACL	*ppDACL,
	PACL	*ppSACL,
	PSECURITY_DESCRIPTOR	*ppSecurityDescriptor);

请注意此函数只有第一个参数与GetSecurityInfo不同,这个参数取得一个指向包含系统之对象名称字串的指标。系统中的安全对象较少被命名。此函数有时候比GetSecurityInfo更方便,因为它不要求您先取得对象的handle。有关可以使用GetNamedSecurityInfo撷取安全性的对象清单,请参阅 表格10-7 。

经由使用GetNamedSecurityInfo而建立较小的程序代码片段,先前所提的两个程序代码片段可以被合并:

PSECURITY_DESCRIPTOR pSD; 
PSID pSID;
PACL pDACL;
ULONG lErr = GetNamedSecurityInfo(TEXT("C://Test//Test.txt"),
	SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, &pSID, NULL,
	&pDACL,NULL, &pSD);
if (lErr != ERROR_SUCCESS){
	// 错误实例

}

// 清理
LocalFree(pSD);

说明

截至目前为止,我所展示的范例已经使用了C:/Test/Test.txt文件来说明如何撷取对象的安全性资讯。GetNamedSecurityInfo及GetSecurityInfo两者都可以被用来为其他安全对象撷取安全性,包括登录机码、核心对象及使用者对象。有关安全对象的特定安全函数,请参阅 表10-7 。


GetSecurityInfo及GetNamedSecurityInfo可让您传递指标变数的位址,以撷取拥有者SID位址、群组SID、DACL或被传回之安全描述项中的SACL。然而,如果您只撷取对象的安全描述项,则您可能对如何撷取这些资讯感到疑惑。您可以使用GetSecurityDescriptorOwner、GetSecurityDescriptorGroup、GetSecurityDescriptorDacl及GetSecurityDescriptorSacl函数,撷取指向安全描述项内部资料结构的指标。我将在本章稍后的〈安全私人对象〉一节中,进一步讨论这些函数。

DACL剖析
 

我们已经讨论过如何撷取指向安全对象的DACL指标部份,现在是开始揭开读取DACL方式的时候。请记得DACL是个存取控制清单(ACL),而ACL多于指出信任成员拒绝或允许存取的存取控制项目(ACEs)清单。

安全描述项的DACL可以被设定成NULL,以指出目前没有特定对象的DACL,因此被称为「NULL DACL」。如果对象拥有NULL DACL,则所有存取会被暗地授予所有系统的信任成员。


说明

当对象拥有当前的DACL时,所有对对象的存取皆会被暗地拒绝,除非明确地允许。有时候对象会有空的DACL,而这不该与NULL DACL混淆。一个空的对象表示任何人(除了拥有者)都无法对此对象做存取。另一方面,一个NULL DACL允许所有的存取给所有信任成员。


假如有当前的DACL,则撷取DACL中相关ACEs的资讯可能是必需的。首先,您必须经由呼叫GetAclInformation,以找出DACL包含多少ACEs:

BOOL GetAclInformation(
	PACL	pACL,
	PVOID	pACLInformation,
	DWORD	dwACLInformationLength,
	ACL_INFORMATION_CLASS	aclInformationClass);

此函数可让您撷取经由pACL参数传递到这个函数的DACL(或是SACL)相关资讯。您应该传递结构的位址以取得ACL资讯为pACLInformation参数,而结构的长度则为dwACLInformationLength参数。AclInformationClass参数是个列举型态,它指出被传回的资讯类型,也定义您应该传递哪种结构类型为pACLInformation参数。表10-9列出AclInformationClass参数的可能值。

 表10-9 ACL_INFORMATION_CLASS列举型态
列举值 结构 定义
AclRevisionInformationACL_REVISION_INFORMATION传回ACL修订资讯
AclSizeInformationACL_SIZE_INFORMATION传回ACL大小的资讯,包括ACE总数

ACL_SIZE_INFORMATION结构最常与GetAclInformation一起使用,其定义如下:

typedef struct _ACL_SIZE_INFORMATION {
	DWORD AceCount;
	DWORD AclBytesInUse;
	DWORD AclBytesFree;
}ACL_SIZE_INFORMATION;

以下的程序代码片段撷取了C:/Test/Text.txt文件之DACL中的ACES数量。

PSECURITY_DESCRIPTOR pSD; 
PACL pDACL;
ULONG lErr = GetNamedSecurityInfo(TEXT("C://Test//Test.txt"),
	SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL,
	&pDACL, NULL, &pSD);
if (lErr != ERROR_SUCCESS){
	// 错误实例
}

ACL_SIZE_INFORMATION aclSize = {0};
if(pDACL != NULL){
	if(!GetAclInformation(pDACL, &aclSize, sizeof(aclSize),
		AclSizeInformation)){
		// 错误实例
	}
}

ULONG nAceCount = aclSize.AceCount;

说明

假如C:/Test/Test.txt文件存在FAT磁盘上,或是它没有DACL,系统会在pDACL变数中传回NULL。这就是为什么在呼叫GetAclInformation之前测试NULL是很重要的原因。传递NULL指标值到GetAclInformation中,会导致违规存取的情形。


拥有DACL中的ACEs数量后,可以使用这个资讯去撷取个别的ACEs。GetAce的呼叫方式如下。

BOOL GetAce(
	PACL	pACL,
	DWORD	dwACEIndex,
	PVOID	*pACE);

这个函数简单地取得指标,指向ACL、您想要取得ACE以0为基础的索引、以及储存DACL中指向ACE的指标的指标变数位址。

请注意pace参数是PVOID类型。这是因为一些不同的ACE类型属于DACL,而每个类型都由一种不同的结构表示。GetAce被用来撷取每个类型。然而,ACLs可以包含任何数量的ACEs及任何ACE类型的组合,所以在您用GetAce撷取ACE前,不可能知道ACEs的类型。

尽管撷取ACE总数及ACE本身是简单的,研读DACL的真正工作变成去认识不同类型的ACEs。

认识ACEs
 

有些不同的ACE类型可以被分割成两个广泛的类型:标准ACEs及对象ACEs。您可以在每一组中找到允许、拒绝存取和稽核存取的ACE类型。每个ACE类型必定会分享共同的属性-即其结构第一个成员为ACE_HEADER类型,定义如下:

typedef struct _ACE_HEADER {
	BYTE	AceType;
	BYTE	AceFlags;
	USHORT	AceSize;
}ACE_HEADER;

ACE标头对于读取DACL来说是重要的,因为当您从GetAce撷取指向ACE的指标时,在读取ACE的标头前,并不知道ACE的类型或是组成ACE的结构类型。

如您所猜测的,ACE标头的AceType成员指出了标头代表的ACE类型。表10-10列出了AceType可能的值。这六种ACE类型可在Windows 2000中使用。

 表10-10 ACE类型
ACE类型 叙述
ACCESS_ALLOWED_ACE_TYPE在DACL中使用,指出一组明确地授予给信任成员的存取权利,使用ACCESS_ ALLOWED_ACE结构。
ACCESS_DENIED_ACE_TYPE在DACL中使用,指出一组明确地拒绝给予信任成员的存取权利,使用ACCESS_ DENIED_ACE结构。
SYSTEM_AUDIT_ACE_TYPE在SACL(请参阅〈 稽核及SACL 〉一节)中使用,指出要求一组信任成员时,可能会产生稽核事件的存取权利,使用SYSTEM_AUDIT_ACE结构。
ACCESS_ALLOWED_OBJECT_ACE_TYPE在目录服务对象的DACL中使用,指出一组明确授予代表Active Directory中某个对象之信任成员的存取权利,使用ACCESS_ALLOWED_OBJECT_ACE结构。
ACCESS_DENIED_OBJECT_ACE_TYPE在目录服务对象的DACL中使用。指出一组明确拒绝给予代表Active Directory中某个对象之信任成员的存取权利,使用ACCESS_DENIED_OBJECT_ACE结构。
SYSTEM_AUDIT_OBJECT_ACE_TYPE在目录服务对象的SACL(请参阅〈 稽核及SACL 〉一节)中使用,指出要求一组信任成员时,可能产生稽核事件的存取权利,使用SYSTEM_AUDIT_OBJECT_ ACE结构。

ACE_HEADE结构的AceFlags成员包含关于ACE的继承及稽核资讯。AceFlag成员将是表10-11中某些值的组合。

 表10-11 ACE_HEADER结构的AceFlags成员的值
说明
继承标记
INHERITED_ACE如果这个标记是从父对象继承过来的,则在ACE中设定这个标记,这可让系统区别直接作用的ACEs及因为继承而自动作用的ACEs。
CONTAINER_INHERIT_ACE拥有这个ACE的对象,其子系的容器对象将会继承这个ACE,以做为有效的ACE。
 这种继承为预设的,然而,如果也有设定NO_PROPAGATE_INHERIT_ACE标记的话,直系的子系可以设定为停止继承。假如这个位元没有被设定,但是有设定OBJECT_INHERIT_ACE位元,则ACE将会继承容器对象,并为容器的ACE设定INHERIT_ONLY_ACE标记。
OBJECT_INHERIT_ACE这个ACE被继承且作用于自有对象的非容器及子对象。对于容器的子对象,除非也有设定CONTAINER_INHERIT_ACE标记,否则ACE只能如继承ACE一样被继承。
 假如设定了NO_PROPAGATE_INHERIT_ACE标记及CONTAINER_INHERIT_ACE标记,有设定OBJECT_ NHERIT_ACE位元的ACE将不会继承容器对象。
INHERIT_ONLY_ACE这个ACE在拥有ACE的对象上无作用,不过它在子容器或非容器子对象上可能有效。
 它对于有设定INHERIT_ONLY_ACE标记的ACE是无效的,除非CONTAINER_INHERIT_ACE及OBJECT_INHERIT_ACE标记的其中一个或两个有被设定。
NO_PROPAGATE_INHERIT_ACE在ACE中设定这个标记会使它只能被继承一次。继承的ACEs将清除它们的OBJECT_INHERIT_ ACE及CONTAINER_INHERIT_ACE标记,以至于新的ACEs不会被继承。
稽核标记
FAILED_ACCESS_ACE_FLAG在SACL中,假如存取被要求,且结果是ACCESS_DENIED时,设定这个位元的ACE将产生稽核事件。
SUCCESSFUL_ACCESS_ACE_FLAG与SACL中的系统稽核ACEs一起使用,为成功的存取尝试产生稽核讯息。

除了ACE类型及标记之外,ACE_HEADER结构的AceSize成员会指出讨论中的ACE大小。如您所见的,ACE_HEADER包含了关于ACE的丰富资讯数量。事实上,在呼叫GetAce时,如果您只要撷取有关ACE的继承及类型资讯时,可以不用看到太深入的ACE_HEADER结构。以下的程序代码片段显示了如何呼叫GetAce,以撷取ACE的类型:

ACE_HEADER* pACEHeader ; 
GetAce(pDACL, 0, (PVOID*)&pACEHeader);

switch(pACEHeader->AceType ){
	// 根据ACE的类型作用
}

既然您在ACE中通常不只要求类型及继承式样的资讯,您必须探寻ACE_HEADER的内容。有两种主要的ACEs类型—标准及对象,标准ACEs不是对象ACEs的ACEs;对象ACEs只与Active Directory一起使用。它们被称为对象ACEs是因为它们包含了GUIDs,GUIDs使它们与目录服务可得的许多对象类型一起工作成为可能。标准ACEs无疑是最常见的,所以我们将首先讨论。

 标准ACEs 系统中大部分的安全对象专门处理标准ACEs。存在于Active Directory的对象是例外的情形,它被称为 目录服务对象 

就像所有的ACEs一般,标准ACEs由叁种结构来表示,分别是允许存取、拒绝存取及稽核。以下是每个结构的定义:

typedef struct _ACCESS_DENIED_ACE {
	ACE_HEADER	Header;
	ACCESS_MASK	Mask;
	DWORD	SidStart;
}ACCESS_DENIED_ACE;
typedef struct _ACCESS_ALLOWED_ACE {
	ACE_HEADER	Header;

	ACCESS_ MASK	Mask;
	DWORD	SidStart;
}ACCESS_ALLOWED_ACE;
typedef struct _SYSTEM_AUDIT_ACE {
	ACE_HEADER	Header;
	ACCESS_ MASK	Mask;
	DWORD	SidStart;
}SYSTEM_AUDIT_ACE;

每个结构的定义是完全相同的,都包括允诺的Header成员,即目前熟悉的ACE_HEADER类型。

剩下的两个成员相当容易了解。第一个是遮罩,是一个指出存取权利被拒绝、允许或稽核的32位元遮罩。

接下来将立即讨论存取权利的重要细节,但是请记得对一个给定的对象类型,其存取权利被分为通用、标准及特殊权利,并被组合成个别的32位元值。这个32位元值是ACE的 遮罩 成员。有关存取遮罩格式,请看 

ACE结构的最后一个成员是SidStart,它指出ACE拒绝、允许或稽核存取这个对象的信任成员帐户的SID起点。这个成员需要稍作讨论。

请记得SID(在第九章讨论过)是个可变长度的二进制结构,它指出系统的信任成员帐户。因为每个ACE包含SID,ACE结构也是一个可变长度的结构。SidStart成员是一个包含在ACE中的SID起点的预留位置。SidStart值(及类型)是不恰当且不应该被存取的,因为它实际上是SID结构的第一对位元组。

您可能会发现以下的巨集指令对于从ACE结构取得SID是很有用的:

#define PSIDFromPACE(pACE)((PSID)(&((pACE)->SidStart)))

这个特殊的巨集指令取得指向任何ACE结构的指标-即对象或标准ACE-并且传回指向ACE的SID结构的指标。

标准ACE类型被分成叁种不同的结构,当您为ACL建立ACEs时,主要被用来作为逻辑的预留位置。然而,因为每个ACE结构是完全相同的,从DACL读取ACEs时,常见的情形是只编写使用其中一个ACE类型的程序代码,而不使用ACE_HEADER结构的AceType成员来维护ACE类型。以下的函数使用了此种技巧,并印出DACL中每个ACE的资讯:

void DumpACL( PACL pACL ){ 
	__try{
		if (pACL == NULL){
			_tprintf(TEXT("NULL DACL/n"));
			__leave;
		}

		ACL_SIZE_INFORMATION aclSize = {0};

		if (!GetAclInformation(pACL, &aclSize, sizeof(aclSize),
			AclSizeInformation))
			__leave;
		_tprintf(TEXT("ACL ACE count: %d/n"), aclSize.AceCount);

		struct{
			BYTE lACEType;
			PTSTR pszTypeName;
		}aceTypes[6] == {
			{ACCESS_ALLOWED_ACE_TYPE, TEXT("ACCESS_ALLOWED_ACE_TYPE")},
			{ACCESS_DENIED_ACE_TYPE, TEXT("ACCESS_DENIED_ACE_TYPE")},
			{SYSTEM_AUDIT_ACE_TYPE,TEXT("SYSTEM_AUDIT_ACE_TYPE")},
			{ACCESS_ALLOWED_OBJECT_ACE_TYPE,
				TEXT("ACCESS_ALLOWED_OBJECT_ACE_TYPE")},
			{ACCESS_DENIED_OBJECT_ACE_TYPE,
				TEXT("ACCESS_DENIED_OBJECT_ACE_TYPE")},
			{SYSTEM_AUDIT_OBJECT_ACE_TYPE,
				TEXT("SYSTEM_AUDIT_OBJECT_ACE_TYPE")}};
	struct{
		ULONG lACEFlag;
		PTSTR pszFlagName;
	}aceFlags [7 ] =={
		{INHERITED_ACE, TEXT("INHERITED_ACE")},
		{CONTAINER_INHERIT_ACE, TEXT("CONTAINER_INHERIT_ACE")},
			{OBJECT_INHERIT_ACE, TEXT("OBJECT_INHERIT_ACE")},
			{INHERIT_ONLY_ACE, TEXT("INHERIT_ONLY_ACE")},
			{NO_PROPAGATE_INHERIT_ACE, TEXT("NO_PROPAGATE_INHERIT_ACE")},
			{FAILED_ACCESS_ACE_FLAG, TEXT("FAILED_ACCESS_ACE_FLAG")},
			{SUCCESSFUL_ACCESS_ACE_FLAG,
				TEXT("SUCCESSFUL_ACCESS_ACE_FLAG")}};
		for (ULONG lIndex = 0; lIndex < aclSize.AceCount; lIndex++){
			ACCESS_ALLOWED_ACE* pACE;
			if (!GetAce(pACL, lIndex, (PVOID*)&pACE))
				__leave;
			_tprintf(TEXT("/nACE #%d/n"), lIndex);
			ULONG lIndex2 = 6;
			PTSTR pszString = TEXT("Unknown ACE Type");
			while (lIndex2--){
				if(pACE->Header.AceType == aceTypes [lIndex2 ].lACEType){
					pszString = aceTypes[lIndex2].pszTypeName;
				}
			}
			_tprintf(TEXT(" ACE Type =/n  /t%s/n"), pszString);
			_tprintf(TEXT("  ACE Flags = /n"));
			lIndex2 = 7;
			while (lIndex2--){
				if ((pACE->Header.AceFlags &aceFlags [lIndex2 ].lACEFlag)
					!=0)
					_tprintf(TEXT("  /t%s/n"),
						aceFlags[lIndex2].pszFlagName);
			}

			_tprintf(TEXT(" ACE Mask ((32->0)=/n /t "));
			lIndex2 = (ULONG)1<<31;
			while (lIndex2){
				_tprintf(((pACE->Mask &lIndex2) != 0)?TEXT("1"):TEXT("0"));
				lIndex2>>=1;
			}

			TCHAR szName[1024];
			TCHAR szDom[1024];
			PSID pSID = PSIDFromPACE(pACE);
			SID_NAME_USE sidUse;
			ULONG lLen1 = 1024, lLen2 = 1024;
			if (!LookupAccountSid(NULL, pSID,
				szName, &lLen1, szDom, &lLen2, &sidUse))
				lstrcpy(szName, TEXT("Unknown"));

			PTSTR pszSID;
			if (!ConvertSidToStringSid(pSID, &pszSID))
				__leave;
			_tprintf(TEXT("/n  ACE SID = /n  /t%s (%s)/n"), pszSID, szName);
			LocalFree(pszSID);
		}
	}__finally{}
}

ACEs的所有ACE类型倾印了ACCESS_ALLOWED_ACE结构,这个结构对任何标准ACE类型(尽管这个函数对对象ACEs将失去作用)都能作用,因为所有标准ACE结构都相同。

以下的程序片段可以与DumpACL函数一起被用来显示NTFS共享目录中的ACEs:

PSECURITY_DESCRIPTOR pSD; 
PACL pDACL;
ULONG lErr = GetNamedSecurityInfo(TEXT("C://Test//Test"),
	SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL,
	&pDACL, NULL, &pSD);
if (lErr == ERROR_SUCCESS){
	DumpACL(pDACL);
}

说明

DumpACL范例函数说明了如何读取DACL中的ACE资讯。您会在自己编写的程序代码中发现像这样有用的程序代码。您也会发现以DumpACL函数作为除错工具是很有帮助的,尤其是当您开始编写程序代码去修改安全对象的DACL时。在您作了修改的前后,使用它来倾印对象的DACL内容,以查看您的程序代码是否产生了所要求的结果。

这种技巧胜过使用浏览器的安全属性页检视安全性,因为系统的使用者介面不会逐字显示对象的ACE资讯。事实上,使用者介面不会显示没有事先在DACL中适当地安排ACEs顺序的DACL。


因为ACE结构的性质可让您发现它对定义一个表示每个ACE类型的Union很有帮助。于是就一个指向Union类型的指标来说,您可以处理从GetAce传回的ACEs。

以下的程序代码显示一个使用此类技巧的范例:

typedef union _ACE_UNION{
	ACE_HEADER	aceHeader;
	ACCESS_ALLOWED_ACE	aceAllowed;
	ACCESS_DENIED_ACE	aceDenied;
	SYSTEM_AUDIT_ACE	aceAudit;
}*PACE_UNION;
PACE_UNION pACE ;
GetAce(pDACL, 0, (PVOID*)&pACE);

switch (pACE->aceHeader.AceType){
.
.
.

 对象ACEs 除非您要编写保护及修改Active Directory对象安全性的程序代码,否则您不可能会在自己的应用程序中使用对象ACEs。然而,Active Directory是Windows 2000的一个重要元件,了解它保护对象安全的方法并没有害处。我们不会花太多时间在这个主题上,此处的说明只是为了避免混淆您对标准ACEs的认识。

就像标准ACES一样,对象ACEs有叁种结构:拒绝、允许及稽核信任成员存取权利。以下是对象ACEs的结构:

typedef struct _ACCESS_ALLOWED_OBJECT_ACE {
	ACE_HEADER		Header;
	ACCESS_ MASK	Mask;
	DWORD	Flags;

	GUID	ObjectType;
	GUID	InheritedObjectType;
	DWORD	SidStart;
} ACCESS_ALLOWED_OBJECT_ACE, *PACCESS_ALLOWED_OBJECT_ACE;
typedef struct _ACCESS_DENIED_OBJECT_ACE {
	ACE_HEADER	Header;
	ACCESS_MASK	Mask;
	DWORD	Flags;
	GUID	ObjectType;
	GUID	InheritedObjectType;
	DWORD	SidStart;
} ACCESS_DENIED_OBJECT_ACE, *PACCESS_DENIED_OBJECT_ACE;
typedef struct _SYSTEM_AUDIT_OBJECT_ACE {
	ACE_HEADER	Header;
	ACCESS_MASK	Mask;
	DWORD	Flags;
	GUID	ObjectType;
	GUID	InheritedObjectType;
	DWORD	SidStart;
} SYSTEM_AUDIT_OBJECT_ACE, *PSYSTEM_AUDIT_OBJECT_ACE;

就像标准ACEs一样,除了附加的Flags、ObjectType及InheritedObjectType成员外,每个结构类型及结构的成员都相同。

注意不要把对象ACE结构的Flags成员与ACE_HEADER结构的AceFlags成员混淆了。Flags成员可以为0或是表10-12中任何值的组合。


说明

和Win32 SDK中类似的结构不同,表10-12中的标记不指出结构中是否有使用ObjectType及InheritedObjectType成员,而是指出这些成员是否真的存在于结构中!

没有固定成员清单的结构被称为 定形的(amorphous) 。每个对象ACE结构都是无定形的,在Win32 SDK中是非常罕见的结构。这显然是对象ACEs最难处理的一面。


 表10-12 对象ACE标记成员的值
叙述
ACE_OBJECT_TYPE_PRESENT指出ObjectType成员存在于对象ACE结构中
ACE_INHERITED_OBJECT_TYPE_PRESENT指出InheritedObjectType成员存在于对象ACE结构中

ObjectType及InheritedObjectType都是GUIDs。假如您不熟悉GUIDs,可以在《Platform SDK》文件或《Inside COM》(Dale Rogerson, Microsoft Press, 1997)找到这个主题的完整讨论。

就我们所需目标而言,您只需要懂得GUID是一个128位元的值,几乎无论什么东西都可以用它来作为唯一地识别。

幸运的是,Active Directory服务介面(Active Directory Service Interfaces, ADSI)为了在Active Directory对象上操作安全性而提供了使用介面,它负责从您的程序代码中移除处理对象ACEs。由于此原因,使得您需要编写在DACL中直接操作对象ACEs程序代码的机率非常小。有关这个主题的更多资讯,请参阅《Platform SDK》中对IADsSecurityDescriptor、IADsAccessControlList及IADsAccessControlEntry介面部份的讨论。


说明

在Active Directory外的安全对象ACLs中从未发现对象ACEs,对象ACEs使得ACL操作程序代码变得更复杂。由于这个原因,本章其馀的部分(及本章所有的程序代码)皆假定ACLs不包含对象ACEs。没有对象ACEs的ACLs减轻了我身为作者的工作,也简化让您了解复杂主题的工作。


存取权利
 

存取权利起先看起来似乎是个需要理解的简单观点,但是有某些重点可能不够明显。要了解存取权利之细微差别的最好方法是讨论它们被使用在哪里并检查每个群组。让我们先复习已经知道的部分:

  • 系统在代表信任成员的安全对象上执行任何安全性作业之前,信任成员必须持有存取权利的组合。
     
  • 所有存取权利都被封装成一个32位元的存取遮罩(请察看 
  • 存取权利被分割成叁类:通用权利(3位元1-28)、标准权利(位元22-16)及特定权利(位元15-0)。
     
  • 存取遮罩中的两个「奇数」位元不符合存取权利群组:即稽核位元及最大的允许位元。
     
图10-2 )。
 

截至目前为止,我们已经讨论过储存在ACE中的存取遮罩之存取权利,它只是交易处理的其中一部份。我说过权利是执行工作所需的,但是并没有明确说明应如何向系统要求权利。要求权利是交易处理的另一部份。

通常当您在向系统要求一个对象的handle时,即是在要求一个权利。大部分传回安全对象handle的函数皆有一个必须的存取参数,您必须传递一个值以指出您打算如何使用此handle。请看以下的范例:

HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, TEXT("MyEvent"));

您可能已经非常熟悉像这样的程序代码,但是却怎么也没想到您是在向系统要求这个事件对象的特定存取权利。您传递到OpenEvent(及其他类似的函数)存取参数的标记位元与储存在保护对象安全之ACEs的位元是相同的。事实上,系统在传回事件的handle前就会执行存取检查。


说明

Windows中有许多安全对象皆是透过handles被存取。当这些对象的handle被要求时会检查其安全性,而您的权利会和此handle一起被储存。这意味着一旦您已经收到某个对象的handle时,对象本身的存取权利可能会改变,而经由handle提供给您的存取则仍是相同的,直到handle被结束为止。


某些在对象上执行安全活动的handle是您从未见过的。这个理想的范例即是呼叫GetNamedSecurityInfo,此函数在您不须先撷取某个对象handle时很方便。在这个实例中,系统内部在执行任何安全性活动前,会先执行存取检查。

您现在已经熟悉了ACE及存取权利的存取要求,让我们开始讨论更详细的存取权利类型。首先,我们需要在不同的存取遮罩中取得「奇数」位元。

稽核位元代表存取权利ACCESS_SYSTEM_SECURITY,在使用者可以修改对象的SACL前,它必须由信任成员持有(本章稍后将会更详细讨论稽核的内容)。

最大的允许位元代表「非存取权利」MAXIMUM_ ALLOWED,我称它为非存取权利是因为它从不会在储存于DACL的ACE中发现。只在呼叫函数要求存取时被使用。

例如,以下的程序代码片段在撷取指定事件对象的handle时使用MAXIMUM_ALLOWED标记:

HANDLE hEvent = OpenEvent(MAXIMUM_ALLOWED, FALSE, TEXT("MyEvent"));

在这个实例中,系统会在对象上执行存取检查,而非允许或拒绝存取检查,它会传回授予您这个对象之最大权利的存取遮罩。这似乎是个非常方便的特色(在某些情况下它是),当要求一个handle时,它允许您总是传递MAXIMUM_ ALLOWED,而不强迫您正确地描绘所需对象的哪个权利。然而,有一个不用如此做的好理由。

当您取得对象的handle时,通常会被系统告知您被允许以某些方法使用这个对象。假如您在要求handle时被拒绝,通常即表示着您不被允许作所要求的任何事,此时您的软件可以采取适当的方法。

假如您每次需要存取时皆传递MAXIMUM_ALLOWED到对象上,那么您的程序代码在一段长时间内可能会运作良好。但是当您的程序第一次遇到没有授予足够存取的对象时,您的软件在撷取对象handle前,可能不会抓取错误。执行失败的函数可能甚至不传回ERROR_ACCESS_DENIED。函数会传回另一个错误,例如ERROR_INVALID_PARAMETER,是很有可能的事,因为您传递了一个handle以及对象的不充分存取权限给它。有这种问题的程序代码可能更难除错。

 标准存取权利 系统定义了五个标准权利。除了这些之外,另外五个「复合的」存取权利则由系统定义对应到五个标准权利的某些组合。

并非所有的标准权利都与系统之所有安全对象有关,但是每个标准权利都是特殊的,因为其本身的涵义不是从对象变成对象。例如,不是所有的对象都能被删除,所以标准权利DELETE对某些对象来说没有意义。然而,对于可以被删除的对象来说,标准权利DELETE可从一个对象类型传递相同的涵义到下一个可删除对象权利的持有者。表10-13列出所有标准及复合权利。

 表10-13 标准及复合权利
权利 说明
标准权利
DELETE
(位元16)
删除存取。
READ_CONTROL
(位元17)
读取存取安全对象的安全性资讯,包括拥有者 SID、群组SID、DACL及安全描述项修正和控 制。它不包括要求读取安全对象SACL的存取。
WRITE_DAC
(位元18)
写入存取安全对象的DACL及群组SID。WRITE_ DAC存取被暗示性地授予对象的拥有者。
WRITE_OWNER
(位元19)
写入存取安全对象的拥有者SID。除非您也持有 SE_BACKUP_NAME权利,否则您只能写入只表 示使用者SID的SID或是群组SIDs的其中一个 到对象的拥有者SID。
SYNCHRONIZE
(位元20)
同步存取可被想成是伺候对象的权利。此类对象 可以是同步化对象(例如,事件或Mutex),或 者它可以是可等待核心对象的其中之一(例如, 文件或处理程序handle)。
复合的标准权利
STANDARD_RIGHTS_READ
STANDARD_RIGHTS_WRITE
STANDARD_RIGHTS_EXECUTE
对应到READ_CONTROL存取。
STANDARD_RIGHTS_REQUIRED对应到位元16-19,或是DELETE、READ_ CONTROL、WRITE_DAC及WRITE_OWNER。 这种存取权利通常包括为特定对象定义的「所有 存取」,例如,EVENT_ALL_ACCESS或FILE_ ALL_ACCESS。
STANDARD_RIGHTS_ALL定义为0x001F0000,这种存取权利包括所有标准权利。

如同表10-13所示,标准权利包括一些强大的权利。例如,假设您持有对对象的WRITE_OWNER存取权限,但却被所有其他的对象拒绝,表示您可能无法作这么多。但是如果您选择使用这个存取设定对象的拥有者SID为您的SID,您就拥有内含的WRITE_DAC存取。然后您可以使用这个存取修改对象的DACL,以允许您自己得到对这个对象所有想要的存取。

 特定权利 特定权利占据了存取遮罩的位元15-0,因此每个系统中的安全对象可以拥有16位定义的不同存取权利。

因为特定权利和对象至对象的情形不同,与特定权利一起操作的最大挑战是找出对象可用之所有特定权利的广泛清单。您应该在两个地方寻找特定权利的详细说明:《Platform SDK》文件及《Platform SDK》标头档。您应该检查有兴趣之现存对象类型的handle函数。例如,假设您对于找出登录机码之可用特定权利感兴趣,则您会因为RegOpenKeyEx函数而查对《Platform SDK》文件,您会找到例如KEY_READ、KEY_SET_VALUE及KEY_ALL_ACCESS的权利叙述。在您有了某个对象的几个存取权利名称之后,便可以在《Platform SDK》所包含的目录中搜寻标头档,它定义了一个或更多个您知道的特定权利,然后您可以找出那个对象剩下的存取权利。

表10-14列出Windows最常见的安全对象之特定权利。

SPECIFIC_RIGHTS_ALL被定义为0x0000FFFF,包括所有的位元16,在存取遮罩中预留给特定权利。

 表10-14 通用安全对象的特定权利
对象类型 特定权利
文件(WinNT.h) 
 
FILE_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x1FF)
		(0x1FF包括所有目前为文件定义的标准权利)
	FILE_READ_DATA

	FILE_WRITE_DATA

	FILE_APPEND_DATA
	FILE_READ_EA
	FILE_WRITE_EA
	FILE_EXECUTE
	FILE_READ_ATTRIBUTES
	FILE_WRITE_ATTRIBUTES
目录(WinNT.h) 
 
FILE_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x1FF)
		(0x1FF包括所有目前为文件定义的标准权利)
	FILE_LIST_DIRECTORY
	FILE_ADD_FILE
	FILE_ADD_SUBDIRECTORY
	FILE_READ_EA
	FILE_WRITE_EA
	FILE_TRAVERSE
	FILE_DELETE_CHILD
	FILE_READ_ATTRIBUTES
	FILE_WRITE_ATTRIBUTES
服务(WinSvc.h) 
 
SERVICE_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SERVICE_QUERY_CONFIG |
		SERVICE_CHANGE_CONFIG |
		SERVICE_QUERY_STATUS |
		SERVICE_ENUMERATE_DEPENDENTS |
		SERVICE_START |
		SERVICE_STOP |
		SERVICE_PAUSE_CONTINUE |
		SERVICE_INTERROGATE |
		SERVICE_USER_DEFINED_CONTROL)
	SERVICE_CHANGE_CONFIG
	SERVICE_ENUMERATE_DEPENDENTS
	SERVICE_INTERROGATE
	SERVICE_PAUSE_CONTINUE
	SERVICE_QUERY_CONFIG
	SERVICE_QUERY_STATUS
	SERVICE_START
	SERVICE_STOP
	SERVICE_USER_DEFINED_CONTROL
打印机(WinSpool.h) 
 
SERVER_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SERVER_ACCESS_ADMINISTER |
		SERVER_ACCESS_ENUMERATE)
	PRINTER_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		PRINTER_ACCESS_ADMINISTER |
		PRINTER_ACCESS_USE)

	JOB_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		JOB_ACCESS_ADMINISTER)
	SERVER_ACCESS_ADMINISTER
	SERVER_ACCESS_ENUMERATE
	PRINTER_ACCESS_ADMINISTER
	PRINTER_ACCESS_USE
	JOB_ACCESS_ADMINISTER
登录机码(WinNT.h) 
 
KEY_ALL_ACCESS
		(STANDARD_RIGHTS_ALL |
		KEY_QUERY_VALUE |
		KEY_SET_VALUE |
		KEY_CREATE_SUB_KEY |
		KEY_ENUMERATE_SUB_KEYS |
		KEY_NOTIFY |
		KEY_CREATE_LINK)
	KEY_QUERY_VALUE
	KEY_SET_VALUE
	KEY_CREATE_SUB_KEY
	KEY_ENUMERATE_SUB_KEYS
	KEY_NOTIFY
	KEY_CREATE_LINK
分享对象(LMShare.h) 
 
PERM_FILE_READ
	PERM_FILE_WRITE
	PERM_FILE_CREATE
处理程序(WinNT.h) 
 
PROCESS_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0xFFF)
		(0xFFF包括所有目前为处理程序定义的标准权利)
	PROCESS_TERMINATE
	PROCESS_CREATE_THREAD
	PROCESS_SET_SESSIONID
	PROCESS_VM_OPERATION
	PROCESS_VM_READ
	PROCESS_VM_WRITE
	PROCESS_DUP_HANDLE
	PROCESS_CREATE_PROCESS
	PROCESS_SET_QUOTA
	PROCESS_SET_INFORMATION
	PROCESS_QUERY_INFORMATION
线程(WinNT.h) 
THREAD_ALL_ACCESS
 
(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x3FF)
		(0x3FF包括所有目前为线程定义的标准权利)
	THREAD_TERMINATE
	THREAD_SUSPEND_RESUME
	THREAD_GET_CONTEXT
	THREAD_SET_CONTEXT
	THREAD_SET_INFORMATION
	THREAD_QUERY_INFORMATION
	THREAD_SET_THREAD_TOKEN
	THREAD_IMPERSONATE
	THREAD_DIRECT_IMPERSONATION
工作 (WinNT.h) 
 
JOB_OBJECT_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x1F)
		(0x1F包括所有目前为工作对象定义的标准权利)
	JOB_OBJECT_ASSIGN_PROCESS
	JOB_OBJECT_SET_ATTRIBUTES
	JOB_OBJECT_QUERY
	JOB_OBJECT_TERMINATE
	JOB_OBJECT_SET_SECURITY_ATTRIBUTES
号志(Semaphore)(WinNT.h) 
 
SEMAPHORE_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x3)
		(0x3包括所有目前为号志(Semaphore)定义的标准权利)
	SEMAPHORE_MODIFY_STATE
	MUTANT_QUERY_STATE
事件(WinNT.h) 
 
EVENT_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x3)
	(0x3包括所有目前为事件定义的标准权利)
	EVENT_MODIFY_STATE
	MUTANT_QUERY_STATE
Mutex(WinBase.h) 
 
MUTEX_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x3)
		(0x3包括所有目前为Mutexes定义的标准权利)
	MUTEX_MODIFY_STATE
	MUTANT_QUERY_STATE
文件对应对象(WinBase.h) 
 
FILE_MAP_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		FILE_MAP_COPY |
		FILE_MAP_WRITE |
		FILE_MAP_READ |
		SECTION_MAP_EXECUTE
		SECTION_EXTEND_SIZE)

	FILE_MAP_WRITE
	FILE_MAP_READ
	FILE_MAP_COPY
	SECTION_EXTEND_SIZE
可等待的计时器(WinNT.h) 
 
TIMER_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		TIMER_QUERY_STATE |
		TIMER_MODIFY_STATE)
	TIMER_QUERY_STATE
	TIMER_MODIFY_STATE
Token (WinNT.h) 
 
TOKEN_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		TOKEN_ASSIGN_PRIMARY |
		TOKEN_DUPLICATE |
		TOKEN_IMPERSONATE |
		TOKEN_QUERY |
		TOKEN_QUERY_SOURCE |
		TOKEN_ADJUST_PRIVILEGES |
		TOKEN_ADJUST_GROUPS |
		TOKEN_ADJUST_SESSIONID |
		TOKEN_ADJUST_DEFAULT)
	TOKEN_ASSIGN_PRIMARY
	TOKEN_DUPLICATE
	TOKEN_IMPERSONATE
	TOKEN_QUERY
	TOKEN_QUERY_SOURCE
	TOKEN_ADJUST_PRIVILEGES
	TOKEN_ADJUST_GROUPS
	TOKEN_ADJUST_DEFAULT
	TOKEN_ADJUST_SESSIONID
管道(WinNT.h) 
 
FILE_ALL_ACCESS
		(STANDARD_RIGHTS_REQUIRED |
		SYNCHRONIZE |
		0x1FF)
		(0x1FF包括所有目前为文件定义的标准权利)
	FILE_READ_DATA
	FILE_WRITE_DATA
	FILE_CREATE_PIPE_INSTANCE
	FILE_READ_ATTRIBUTES
	FILE_WRITE_ATTRIBUTES
Window站台(WinUser.h) 
 
WINSTA_ACCESSCLIPBOARD
	WINSTA_ACCESSGLOBALATOMS
	WINSTA_CREATEDESKTOP
	WINSTA_ENUMDESKTOPS
	WINSTA_ENUMERATE
	WINSTA_EXITWINDOWS
	WINSTA_READATTRIBUTES
	WINSTA_READSCREEN
	WINSTA_WRITEATTRIBUTES
桌面(WinUser.h) 
 
DESKTOP_CREATEMENU
	DESKTOP_CREATEWINDOW
	DESKTOP_ENUMERATE
	DESKTOP_HOOKCONTROL
	DESKTOP_JOURNALPLAYBACK
	DESKTOP_JOURNALRECORD
	DESKTOP_READOBJECTS
	DESKTOP_SWITCHDESKTOP
	DESKTOP_WRITEOBJECTS

包括这些特定权利及先前讨论的标准权利在内,您已经拥有保护对象安全所要求的所有权利。然而,还有一些通用权利的议题需要讨论。

 通用权利及预设的安全性 本章稍早曾提到软件在建立一个没有明显设定安全性的对象时,会指派预设的安全性给安全对象。这个指派的特点通常是经由传递NULL给函数的安全属性参数,以建立一个安全对象,例如文件或事件。在我们可以开始讨论通用权利之前,您必须了解如何实作预设安全性的内容。

请记得您执行的程序代码与权杖(Token)的内部结构相关联。到目前为止,已经说明了权杖包含您的识别SID及群组SIDs,以及指派给您的权限清单。除了这个资讯外,每个权杖也储存一个以预设安全性建立对象的DACL,即 预设的DACL ,可以由您的程序代码设定(这个和其他与权杖相关的主题将在下一章中做更详细的讨论)。

目前,您需要了解已存在的DACL(您可以修改及设定),它可以适用于预设安全性建立的对象。要注意的重要部份是DACL可以适用于对象的任何类型,因而产生了一个系统方面的问题。


说明

DACL及标准ACE结构都不维持它们所保护的对象类型资讯。这使得为预设DACL建立ACEs变得更复杂,因为如果应用于另一个对象类型,某个对象类型的特定权利可能会是不适当的。Windows的设计者经由建立通用权利来解决这个问题。


通用权利的本意是在权杖的预设DACL中使用。系统定义这些通用权利为GENERIC_READ、GENERIC_WRITE、GENERIC_EXECUTE及GENERIC_ALL。当预设的DACL被应用于对象时,系统会将从DACL的ACEs中找到的任何通用权利对应到适合该对象之标准及特定权利的组合。这种方式能适当地影响使用该ACE之任何对象安全性的ACE,并且可以被加入预设的DACL。


说明

您不能直接指派DACL给包含ACEs的对象,因为ACEs的存取遮罩包括通用权利。系统在设定对象的安全性之前,会自动地从ACE中清除这些权利。



说明

要求存取某个对象时,您可以指定通用权利,但是最好不要这样做。要求符合您程序代码所需要的标准及特定权利会较更适当的方法。


通晓了您所知道的通用权利后,您可能会对于如何找出系统中,特殊对象所对应的通用权利感到疑惑。这是个很好的问题,唯一令人满意的答案即是建立一个拥有ACE之预设DACL,其ACE包括了一个个别的通用权利(如GENERIC_READ),然后再建立一个对象并检查所产生的ACE(请参阅 

 表10-15 常见安全对象的一般对应
对象类型 通用权利 标准及特定对应的权利
文件  
 
GENERIC_READ 
GENERIC_WRITE 
GENERIC_EXECUTE 
GENERIC_ALL
FILE_GENERIC_READ 
FILE_GENERIC_WRITE 
FILE_GENERIC_EXECUTE 
FILE_ALL_ACCESS
目录  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
FILE_LIST_DIRECTORY | 
FILE_ADD_FILE
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
FILE_ADD_SUBDIRECTORY | 
FILE_READ_EA
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
FILE_LIST_DIRECTORY | 
FILE _ADD_FILE
 
GENERIC_ALL
STANDARD_RIGHTS_REQUIRED| 
FILE_LIST_DIRECTORY | 
FILE_ADD_FILE| 
FILE_ADD_SUBDIRECTORY |
FILE_READ_EA
服务  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
SERVICE_QUERY_CONFIG| 
SERVICE_QUERY_STATUS| 
SERVICE_ENUMERATE_DEPENDENTS| 
SERVICE_INTERROGATE| 
SERVICE_USER_DEFINED_CONTROL
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
SERVICE_CHANGE_CONFIG
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
SERVICE_START| 
SERVICE_STOP| 
SERVICE_PAUSE_CONTINUE| 
SERVICE_INTERROGATE| 
SERVICE_USER_DEFINED_CONTROL
 
GENERIC_ALL
SERVICE_ALL_ACCESS
打印机  
 
GENERIC_READ 
GENERIC_WRITE
GENERIC_EXECUTE 
GENERIC_ALL
PRINTER_READ 
PRINTER_WRITE 
PRINTER_EXECUTE
PRINTER_ALL_ACCESS
登录机码  
 
GENERIC_READ 
GENERIC_WRITE 
GENERIC_EXECUTE 
GENERIC_ALL
KEY_READ 
KEY_WRITE 
KEY_EXECUTE
KEY_ALL_ACCESS
程序  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
PROCESS_VM_READ| 
PROCESS_QUERY_INFORMATION
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
PROCESS_CREATE_PROCESS| 
PROCESS_CREATE_THREAD| 
PROCESS_VM_OPERATION| 
PROCESS_VM_WRITE| 
PROCESS_DUP_HANDLE| 
PROCESS_TERMINATE|
PROCESS_SET_QUOTA| 
PROCESS_SET_INFORMATION
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE|
SYNCHRONIZE
 
GENERIC_ALL
PROCESS_ALL_ACCESS
线程  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
THREAD_GET_CONTEXT| 
THREAD_QUERY_INFORMATION
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
THREAD_TERMINATE| 
THREAD_SUSPEND_RESUME| 
THREAD_SET_INFORMATION| 
THREAD_SET_CONTEXT
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
SYNCHRONIZE
 
GENERIC_ALL
THREAD_ALL_ACCESS
工作  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
JOB_OBJECT_QUERY
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
JOB_OBJECT_ASSIGN_PROCESS| 
JOB_OBJECT_SET_ATTRIBUTES| 
JOB_OBJECT_TERMINATE
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
SYNCHRONIZE
 
GENERIC_ALL
JOB_OBJECT_ALL_ACCESS
号志(Semaphore)  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
MUTANT_QUERY_STATE
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
SEMAPHORE_MODIFY_STATE
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
SYNCHRONIZE
 
GENERIC_ALL
SEMAPHORE_ALL_ACCESS
事件  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
MUTANT_QUERY_STATE
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
EVENT_MODIFY_STATE
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
SYNCHRONIZE
 
GENERIC_ALL
EVENT_ALL_ACCESS
Mutex  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
MUTANT_QUERY_STATE
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE|
SYNCHRONIZE
 
GENERIC_ALL
MUTEX_ALL_ACCESS
文件对应对象  
 
GENERIC_READ
STANDARD_RIGHTS_READ | 
FILE_MAP_COPY | 
FILE_MAP_READ
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
FILE_MAP_WRITE
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
FILE_MAP_EXECUTE
 
GENERIC_ALL
FILE_MAP_ALL_ACCESS
可等待的计时器  
 
GENERIC_READ
STANDARD_RIGHTS_READ| 
TIMER_QUERY_STATE
 
GENERIC_WRITE
STANDARD_RIGHTS_WRITE| 
TIMER_MODIFY_STATE
 
GENERIC_EXECUTE
STANDARD_RIGHTS_EXECUTE| 
SYNCHRONIZE
 
GENERIC_ALL
TIMER_ALL_ACCESS
权杖(Token)  
 
GENERIC_READ 
GENERIC_WRITE
GENERIC_EXECUTE 
GENERIC_ALL
TOKEN_READ 
TOKEN_WRITE 
TOKEN_EXECUTE 
TOKEN_ALL_ACCESS
Window 站台  
 
GENERIC_READ
WINSTA_ENUMDESKTOPS| 
WINSTA_READATTRIBUTES| 
WINSTA_ENUMERATE | 
WNSTA_READSCREEN| 
STANDARD_RIGHTS_READ
 
GENERIC_WRITE
WINSTA_ACCESSCLIPBOARD| 
WINSTA_CREATEDESKTOP| 
WINSTA_WRITEATTRIBUTES| 
STANDARD_RIGHTS_WRITE
 
GENERIC_EXECUTE
WINSTA_ACCESSGLOBALATOMS |
WINSTA_EXITWINDOWS| 
STANDARD_RIGHTS_EXECUTE
 
GENERIC_ALL
WINSTA_ENUMDESKTOPS| 
WINSTA_READATTRIBUTES| 
WINSTA_ENUMERATE | 
WINSTA_READSCREEN| 
WINSTA_ACCESSCLIPBOARD| 
WINSTA_CREATEDESKTOP| 
WINSTA_WRITEATTRIBUTES| 
WINSTA_ACCESSGLOBALATOMS| 
WINSTA_EXITWINDOWS| 
STANDARD_RIGHTS_REQUIRED
桌面  
 
GENERIC_READ
DESKTOP_READOBJECTS| 
DESKTOP_ENUMERATE| 
STANDARD_RIGHTS_READ
 
GENERIC_WRITE
DESKTOP_WRITEOBJECTS| 
DESKTOP_CREATEWINDOW| 
DESKTOP_CREATEMENU| 
DESKTOP_HOOKCONTROL| 
DESKTOP_JOURNALRECORD| 
DESKTOP_JOURNALPLAYBACK| 
STANDARD_RIGHTS_WRITE
 
GENERIC_EXECUTE
DESKTOP_SWITCHDESKTOP| 
STANDARD_RIGHTS_EXECUTE
 
GENERIC_ALL
DESKTOP_READOBJECTS| 
DESKTOP_WRITEOBJECTS| 
DESKTOP_ENUMERATE| 
DESKTOP_CREATEWINDOW| 
DESKTOP_CREATEMENU| 
DESKTOP_HOOKCONTROL| 
DESKTOP_JOURNALRECORD| 
DESKTOP_JOURNALPLAYBACK| 
DESKTOP_SWITCHDESKTOP| 
STANDARD_RIGHTS_REQUIRED

在我们继续前,必须再提一个预设安全性的重点。假设您要处理的对象,其安全性是继承而来的(例如登录机码或文件),则预设安全性只适用于ACEs,而非从父对象继承而来的对象。

登录及文件系统阶层中,其预设的ACEs是被继承的,所以 预设的安全性 在一般的实例中并不适用。

您现在已经熟悉了所有存取权利的类型,以及包含这些权利的ACE及ACL类型,所以您已经拥有读取及诠释系统中任何对象安全性的所有必要工具。

AccessMaster范例应用程序
 

AccessMaster范例应用程序(「10 AccessMaster.exe」)示范了GetNamed SecurityInfo、SetNamedSecurityInfo、GetSecurityInfo及SetSecurityInfo函数,还有EditSecurity常用对话方块之ISecurityInformation介面的用法。应用程序的原始程序代码及文件存放在随书光碟上的AccessMaster目录中。图10-7显示了使用AccessMaster设定文件安全性的画面。


 

 图10-7 使用AccessMaster设定文件的安全性

除了显示如何计划性地取得及设定系统之一般对象的安全性外,AccessMaster范例应用程序对于了解安全对象的存取控制来说是个很有用的工具。您可以经由名称或程序ID及handle的数值(使用十进位数)来存取对象,也可以用未处理过的二进制数存取遮罩检视ACEs,而非将它们对应到对象的标准及特定权利。

AccessMaster工具也能让您设定系统中安全对象的安全性。因为您可能没有系统中所有对象的权利,所以必须在您可以读取或写入其他安全性资讯之前,取得对象的所有权。

为了完成这个动作,您可以使用第九章的TrusteeMan范例应用程序指派SE_TAKE_OWNERSHIP_NAME权限给您的使用者帐户。

假如您不熟悉系统在对象上存取控制的纵横交错,包括由您的软件建立对象的方式,则您可以使用AccessMaster工具来检视及修改指派给这些对象的存取权利。

设定对象的安全性资讯
 

存取控制的下一个步骤是设定安全对象的安全性。系统中大部分的安全对象是经由呼叫SetSecurityInfo或是SetNamedSecurityInfo而设定(有关安全对象及用来取得及设定安全性的函数清单,请参阅 表10-7 )。

以下是SetSecurityInfo函数的定义:

DWORD SetSecurityInfo(
	HANDLE	handle,
	SE_OBJECT_TYPE	objType,
	SECURITY_INFORMATION	secInfo,
	PSID	psidOwner,
	PSID	psidGroup,
	PACL	pDACL,
	PACL	pSACLl);

而这是SetNamedSecurityInfo函数的定义:

DWORD SetNamedSecurityInfo(
	LPTSTR	pObjectName,
	SE_OBJECT_TYPE	objType,
	SECURITY_INFORMATION	secInfo,
	PSID	psidOwner,
	PSID	psidGroup,
	PACL	pDACL,
	PACL	pSACL);

如您所见,这些函数都很相似,除了SetSecurityInfo拥有设定安全性对象的handle外,而使用SetNamedSecurityInfo设定系统之指定对象的安全性不需要handle。

这些函数也和他们的亲戚非常相像,即GetSecurityInfo及GetNamed SecurityInfo。请注意「SetSecurity」函数的objType参数定义了您正在设定安全性资讯的对象类型与「GetSecurity」函数一样使用相同的对象类型(请参阅 表10-7 )。

secInfo参数指出您要设定的对象安全性描述项元件。这些元件可以是对象的拥有者SID、群组SID、DACL及SACL的任何组合。此外,这些参数被用来设定保护安全性描述项,使它不继承父系的DACLs及SACLs中的ACEs。表10-6显示了所有可以传递给SetNamedSecurityInfo及SetNamedSecurity,以作为secInfo参数的值。您可以结合这些值以确切地指出如何将安全性应用到您的对象及该应用何种安全性到您的对象。

 表10-16 可以传递到SetNamedSecurityInfo及SetNamedSecurity作为secInfo参数的值
叙述
DACL_SECURITY_INFORMATION指出您要为安全对象设定DACL资讯。
SACL_SECURITY_INFORMATION指出您要为安全对象设定SACL资讯。
OWNER_SECURITY_INFORMATION指出您要为安全对象设定拥有者SID资讯。
GROUP_SECURITY_INFORMATION指出您要为安全对象设定群组SID资讯。
UNPROTECTED_DACL_SECURITY__INFORMATION指出您要让父对象中继承的ACEs传播到此物INFORMATION 件的DACL。这个标记必须与DACL_SECURITY 一起使用,不能与PROTECTED_DACL_SECURITY_INFORMATION一起使用。
PROTECTED_DACL_SECURITY_INFORMATION指出您不要让父对象中继承的ACEs传播到此对象的DACL。这个标记必须与DACL_ SECURITY_INFORMATION一起使用,不能与UNPROTECTED_DACL_SECURITY_ INFORMATION一起使用。
UNPROTECTED_SACL_SECURITY_ INFORMATION指出您要让父对象中继承的ACEs传播到这个对象的SACL。这个标记必须与SACL_SECURITY_INFORMATION一起使用,而不能与PROTECTED_SACL_SECURITY_INFORMATION一起使用。
PROTECTED_SACL_SECURITY_INFORMATION指出您不要让父对象中继承的ACEs传播到这个对象的SACL。这个标记必须与SACL_SECURITY _INFORMATION一起使用,不能与UNPROTECTED_SACL_SECURITY_ INFORMATION一起使用。

呼叫SetSecurityInfo或SetNamedSecurityInfo时,根据传递给secInfo参数的标记,您传递给psidOwner、psidGroup、pDACL及pSACL参数的值可能会被忽视,或者分别指示对象的新拥有者SID、群组SID、DACL及SACL。

您应该传递NULL给任何参数,以指出您在安全性描述项中不设定资讯的部分。


说明

必须适当的指出secInfo参数的DACL_SECURITY_INFORMATION或SACL_SECURITY_INFORMATION,并仍旧传递NULL给pDACL或pSACL参数。这表示一个NULL DACL或NULL SACL。


以下的程序代码片段显示使用SetNamedSecurityInfo指派一个NULL DACL到登录机码并保护它的DACL,使不继承父机码的DACL中提供的ACEs。

ULONG lErr = SetNamedSecurityInfo(
	TEXT("Machine//Software//Jason'sKey"), SE_REGISTRY_KEY,
	DACL_SECURITY_INFORMATION|PROTECTED_DACL_SECURITY_INFORMATION,
	NULL, NULL, NULL, NULL);
	if (lErr != ERROR_SUCCESS){
		// 错误实例
	}

说明

请记得NULL DACL指出每个人拥有对此对象的所有存取权(甚至是写入对象安全性的能力)。在这罕见的例子中,设定NULL安全性到一个对象是适当的,您必须保护对象的DACL(或没有DACL),使不继承ACEs。否则,系统会被强迫建立一个包含从父对象继承过来之ACEs的DACL,它破坏了对象之NULL安全性的目的。另一方面,继承安全性的副作用可以是设定对象DACL,包括从父对象继承过来之ACEs的一个简便方法。


如您所见,设定对象的安全性可以是很简单的。然而,当包含了一个有意义之ACEs的DACL时,它可能会变得更困难。不管您是设定新对象(您所建立的)或是已存在之对象的安全性,可以采用两个基本的方法建立对象的DACL:即为对象建立一个新的DACL,或修改一个存在的DACL。

您通常会为新对象建立一个DACL,但并非经常这样。您也可以使用对象的现存DACL,对它作些修改,然后再使用修改过的DACL建立一个跟原始安全对象相同类型的新对象。

同样地,对现存DACL作修改是很常见的,而完全覆盖其安全性则是很少见的。然而,这并不意味着您不能完全取代现存对象的DACL。

因为根据您的需求,其方法可以是弹性的,我将会涵盖两者,以建立一个新DACL的简单任务开始。

建立DACL
 

建立DACL时,您通常会跟随以下的步骤:

  1. 收集您为DACL建立ACEs之信任成员的SIDs。
  2. 经由SIDs使用的大小及您使用的ACE结构大小来计算新DACL的大小。
  3. 为DACL分配内存。
  4. 初始化DACL。
  5. 使用SIDs将ACEs加入DACL,注意在加入允许存取ACEs之前,先加入拒绝存取ACEs。

对于现存的对象,您将会使用到「SetSecurity」函数的其中一个,以把新的DACL应用于对象。对大部分的新对象来说,在呼叫一个建立函数时(如CreateEvent或CreateFile),您也被要求建立及初始化安全描述项及传递一个安全属性结构。

建立DACL最复杂的部分是计算它的大小,CalculateACLSize范例应用程序显示了其方法。使用这个函数,您会传递一个指向SIDs的指标阵列,它被用来计算新DACL所需的大小。我也包含了一个指向现存DACL的选择性指标,若为NULL,它会被忽视,若不是NULL,则会把ACEs的大小加入现存的DACL,然后把现存DACL建立的新DACL大小给您。我马上会谈论到更多,但是现在,请察看CalculateACLSize函数的内容。

ULONG CalculateACLSize( PACL pACLOld, PSID* ppSidArray, int nNumSids, 
	PACE_UNION* ppACEs, int nNumACEs ){
	ULONG lACLSize = 0;
	try{
		// 假如我们包括一个现存的ACL,那么找出它的大小
		if (pACLOld != NULL){
			ACL_SIZE_INFORMATION aclSize;
			if(!GetAclInformation(pACLOld, &aclSize, sizeof(aclSize),
				AclSizeInformation)){
				goto leave;

			}
			lACLSize = aclSize.AclBytesInUse;
		}

		if (ppSidArray != NULL){
			// 逐步浏览每个SID
			while (nNumSids--){
				// 假如SID无效,那么就跳出
				if (!IsValidSid(ppSidArray[nNumSids])){
					lACLSize = 0;
					goto leave;
				}
				// 取得SID的长度
				lACLSize += GetLengthSid(ppSidArray[nNumSids]);
				// 加入ACE结构大小,减去
				// SidStart成员的大小
				lACLSize += sizeof(ACCESS_ALLOWED_ACE)-
					sizeof(((ACCESS_ALLOWED_ACE*)0)->SidStart);
			}
		}

		if (ppACEs != NULL){
			// 逐步浏览每个ACE
			while (nNumACEs--){
				// 取得SIDs长度
				lACLSize += ppACEs[nNumACEs]->aceHeader.AceSize;
			}
		}
		// 加入ACL结构本身
		lACLSize += sizeof(ACL);

	leave:;
	}catch(...){
		// 例外意味着我们这个函数失败
		lACLSize = 0;
	}
	return (lACLSize);
}

说明

CalculateACLSize函数使用了ACCESS_ALLOWED_ACE结构的大小,它为所有标准的ACE类型工作。假如该函数包含了对象ACE类型,将无法正确地计算ACL大小。为了要正确地计算大小,请将这个函数中所提的ACCESS_ALLOWED_ACE完全取代成对象ACE结构,例如ACCESS_ALLOWED_OBJECT_ACE。关于对象ACEs的讨论,请参阅本章稍早的〈 对象ACEs 〉一节。


当您使用如CalculateACLSize的函数时,找出建立一个新的ACL所需要的内存总数是件简单的任务。您应该使用从函数传回的值,用new、HeapAlloc或malloc配置器(Allocator)去分配内存。

在您拥有新ACL的内存后,应该使用InitializeAcl初始化ACL:

BOOL InitializeAcl(
	PACL	pACL,
	DWORD	dwAclLength,
	DWORD	dwAclRevision);

InitializeAcl是个非常简单的函数,它设定了ACL及ACL修订结构中的缓冲器长度。您应该传递ACL_REVISION给dwAclRevision参数。ACL也可以有ACL_REVISION_DS的修订,它指出ACL包含对象ACEs。然而,您不必明确地设定这个修订部份,因为当您把对象ACEs加入ACL时,系统就会更新ACL的修订内容。

在呼叫InitializeAcl后,您的内存缓冲器会包含一个空的ACL,而没有ACEs。假如您想要从现存ACL中移除所有ACEs,也可以使用InitializeAcl。

现在您准备开始把ACEs加入空的ACL中。假定您已经正确地计算已完成的ACL大小,则加入ACEs的方法应该和为拒绝存取ACEs重复呼叫AddAccessDeniedAceEx一样简单,接着可以为您的所有允许存取ACEs呼叫AddAccessAllowedAceEx。

AddAccessDeniedAceEx及AddAccessAllowedAceEx会建立适当类型的ACEs并把它们加入DACL的尾端(假定伴随着ACE的DACL还有空间)。

AddAccessDeniedAceEx定义如下:

BOOL AddAccessDeniedAceEx(
	PACL	pDACL,
	DWORD	dwACERevision,
	DWORD	dwACEFlags,
	DWORD	dwAccessMask,
	PSID	psidTrustee);

这是AddAccessAllowedAceEx的函数定义:

BOOL AddAccessAllowedAceEx(
	PACL	pDACL,
	DWORD	dwACERevision,
	DWORD	dwACEFlags,
	DWORD	dwAccessMask,
	PSID	psidTrustee);

这些函数的宣告完全相同,这样很好。pDACL参数指出您正在加入ACE的DACL。dwACERevision参数应该被设定为ACL_REVISION。dwACEFlags参数则指出将被设定到新ACE的ACE_HEADER之AceFlags成员的值,这被用来指出ACE的继承属性。有关不同的ACE标记,请参阅 表10-11 的说明。您不应传递指出稽核类型的标记给dwACEFlags参数。

dwAccessMask参数指出新ACE的存取遮罩,并说明您拒绝或允许哪些权利给信任成员。最后,您应该传递一个指向SID结构的指标给psidTrustee参数,以指出被拒绝或允许存取的信任成员。

如果DACL的空间不足,AddAccessDeniedAceEx及AddAccessAllowedAceEx函数会传回FALSE,而GetLastError会传回ERROR_ALLOTTED_SPACE_EXCEEDED。


说明

对于不支援继承的对象,您可以使用非「Ex」之AddAccess DeniedAce及AddAccessAllowedAce的简单样式。非「Ex」函数唯一不同的地方是它们不让您为新的ACE设定标记。


当您认识了AddAccessDeniedAceEx及AddAccessAllowedAceEx后,您可以了解重复地呼叫它们去建立一个新DACL并非了不起的事情。在您建立了新的DACL之后,您可以传递它到SetSecurityInfo或SetNamedSecurityInfo,把它用于现存对象中(在您呼叫这些函数的其中一个后,不要忘记为新的DACL释放内存)。

您通常会为即将建立的对象建立一个新的DACL。建立一个对象通常需要您指派新的DACL给安全描述项,然后在呼叫「建立」函数之前,指派安全描述项到一个安全属性结构中。以下的程序代码显示如何为新的事件对象建立一个新的DACL。程序代码会建立并初始化了一个带有DACL的安全描述项,然后再使用它去建立一个指定的事件(请注意,这个程序代码使用了先前的CalculateACLSize函数,以计算新DACL的大小)。这个处理程序的两个挑战即是计算DACL的大小,及以适当的顺序把ACEs加入。

PSID psidEveryone ; 
// 为内建的「Everyone」群组建立一个SID
SID_IDENTIFIER_AUTHORITY sidAuth = SECURITY_WORLD_SID_AUTHORITY;
if (!AllocateAndInitializeSid( &sidAuth, 1, SECURITY_WORLD_RID,
	0, 0, 0, 0, 0, 0, 0, &psidEveryone )){
	// 错误
}

// 我们建立两个ACEs,两者都使用「Everyone」群组
PSID psidArray[2];
psidArray[0] = psidEveryone;
psidArray[1] = psidEveryone;

// 取得新ACL的大小
ULONG lACLSize = CalculateACLSize(NULL, psidArray, 2, NULL, 0);
if (lACLSize == 0){
	// 错误
}

// 分配内存给ACL
PACL pDACL = (PACL)HeapAlloc(GetProcessHeap(), 0, lACLSize);
if (pDACL == NULL){
	// 错误
}

// 初始化ACL
if (!InitializeAcl(pDACL, lACLSize, ACL_REVISION)){
	// 错误
}

// 确定先将拒绝的ACE加入
if (!AddAccessDeniedAce(pDACL, ACL_REVISION,
	WRITE_OWNER|WRITE_DAC, psidArray[0])){
	// 错误
}

// 然后加入允许的ACE
if (!AddAccessAllowedAce(pDACL, ACL_REVISION,
	STANDARD_RIGHTS_ALL|SPECIFIC_RIGHTS_ALL, psidArray[1])){
	GetLastError();//错误「winerror.h」
}

// 分配空间给安全描述项
PSECURITY_DESCRIPTOR pSD = HeapAlloc(GetProcessHeap(), 0,
	SECURITY_DESCRIPTOR_MIN_LENGTH);
if (pSD == NULL){
	// 错误
}
// 我们现在有一个空的安全描述项
if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)){
	// 错误
}
// 指派我们的DACL
if (!SetSecurityDescriptorDacl(pSD, TRUE, pDACL, FALSE)){
	// 错误
}

// 然后从SECURITY_ATTRIBUTES结构指出我们的SD
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = pSD;

// 传递到CreateEvent
HANDLE hEvent = CreateEvent(&sa, TRUE, FALSE, TEXT("SecureEvent"));

// 清除
HeapFree(GetProcessHeap(), 0, pSD);
HeapFree(GetProcessHeap(), 0, pDACL);
FreeSid(psidEveryone);

说明

一个将ACEs以适当顺序加入的可选择方法是编写一个为您自己安排ACL顺序的函数,以使您在加入ACEs时不须担心它的顺序。然后在您把ACL用于对象前,整理全部的ACL。我将在本章稍后实作这样的函数。


这个范例不只显示如何在对象建立时使用DACL及安全描述项,也为带有服务的对象阐明了一个重要的技巧。

本机使用者环境中的服务预设安全性,将会导致例如指定事件及管道的对象被保护,以预防在已登录的使用者安全性环境中执行不被允许的程序。有时候这并非被要求的行为。某些时候您可能想要建立一个拥有NULL DACL的对象,它允许每个人拥有对所有对象的存取权,但这么做会建立一个安全性漏洞。所有的存取包括调整对象安全性的能力,并且允许任何人改变对象的安全性。改变安全性几乎可以肯定会导致对象停止运作。

一个控制存取的较好解决办法即是拒绝每个人存取对象的安全性(只允许对象拥有者修改对象的安全性)。在加入拒绝的ACE之后,您可以允许任何信任成员存取对象,因为您知道会优先检查拒绝ACE的内容。在这个实例中,我允许每个人拥有所有存取权。

修改DACL
 

建立安全对象牵涉到与需要实作服务一样多的存取控制。您可以实作许多服务,而从来不用修改现存对象的安全性,这是令人惊异的。当然,某些程序必须处理已经拥有应用安全性的现存对象。在此种情况下,您的软件必须能够读取及修改对象的DACL。以下是您可以采取的步骤:

  1. 撷取您要修改之对象的DACL。
  2. 假如您正在移除ACEs,在DACL中搜寻这些ACEs并加以删除。
  3. 收集您将加入ACEs的信任成员SIDs。
  4. 建立您将加入的ACEs。
  5. 在DACL中搜寻是否有与您将加入相同的ACEs。若有任何一个存在,将它们从您加入的ACEs群组中移除。
  6. 计算新DACL的大小。
  7. 分配内存及初始化新的DACL。
  8. 复制旧的DACL到新的DACL。
  9. 在新的DACL中将新的ACEs插入适当的位置。
  10. 指派DACL回对象。

如您所见,读取及修改对象DACL的程序并没有那么简单,然而我应告诉您,我已经提供了最糟情况的方案。通常您并非真的移除ACEs,所以可以忽略步骤二的动作。而一般的情形下,您只增加了一个个别的ACE;假如该ACE已经存在DACL中,那么您可以中止整个程序,并简化步骤五的动作。


说明

在ACLs中搜寻已经存在的ACEs是很重要的,虽然完全一样的ACEs并不会影响对象的 安全性 。其原因是ACEs占用了系统中的内存,而某些系统对象(例如,window站台)会出乎意料的持有少数ACEs。每次执行就增加ACE,没有检查工作之必要性的任何软件,最后将耗尽系统的资源。


现在让我告诉您处理读取及修改DACL之工作必须使用的工具。让我们从一个简单的函数开始(您已经知道如何读取DACL中的资讯,这些技巧将在此处开始产生作用)。

BOOL DeleteAce(
	PACL	pACL,
	DWORD	dwACEIndex);

DeleteAce函数会从DACL中移除一个ACE,您必须传递ACL及您要移除的ACE索引值给它。


说明

如果您对这个对象所做的唯一修改只是移除ACEs,那么您不用为新的DACL分配内存。您可以简单地移除经由GetSecurityInfo传回之DACL中的ACEs,然后再放置修改过的DACL回对象。


处理程序的下一个步骤即是要求您拥有便于使用的ACE,因为您将使用到AddAce函数,它会要求一个已建立的ACE;再者,因为您可能会在ACL中搜寻已存在的ACE,此时有个ACE结构可对照DACL之ACEs是方便的。

可惜系统不提供让您使用简便方法建立ACEs的函数。建立ACEs的工作是复杂的,因为每个ACE皆包括了一个SID结构,而SID结构的长度是可变的。最好使用您的SID长度及ACE结构长度分配一个缓冲器给ACE,并复制SID到ACE中,然后设定其ACE栏位。以下的函数显示了实行的方法:

PACE_UNION AllocateACE(ULONG bACEType, ULONG bACEFlags, 
	ULONG lAccessMask,PSID pSID ){
	PACE_UNION pReturnACE = NULL;
	PBYTE pbBuffer = NULL;
	try{
		// 取得ACE中SID的偏移量
		ULONG lSIDOffset = (ULONG)(&((ACCESS_ALLOWED_ACE*)0)->SidStart);
		// 取得没有SID的ACE大小
		ULONG lACEStructSize = sizeof(ACCESS_ALLOWED_ACE)-
			sizeof(((ACCESS_ALLOWED_ACE*)0)->SidStart);
		// 取得SID的长度
		ULONG lSIDSize = GetLengthSid(pSID);

		// 分配一个缓冲器给ACE
		pbBuffer = (PBYTE)LocalAlloc(LPTR, lACEStructSize + lSIDSize);
		if (pbBuffer == NULL)
			goto leave;
		// 复制SID到ACE
		if(!CopySid(lSIDSize, (PSID)(pbBuffer+lSIDOffset), pSID)){
			goto leave;
		}
		pReturnACE = (PACE_UNION) pbBuffer;
		pReturnACE->aceHeader.AceSize = (USHORT)(lACEStructSize + lSIDSize);
		pReturnACE->aceHeader.AceType = (BYTE)bACEType;

		pReturnACE->aceHeader.AceFlags = (BYTE)bACEFlags;
		pReturnACE->aceAllowed.Mask = lAccessMask;
	leave:;
	}catch(...){}
	// 在错误实例中释放缓冲器
	if (pbBuffer != (PBYTE)pReturnACE){
		LocalFree(pbBuffer);
	}
	return (pReturnACE);
}

当在使用AllocateAce时,您只须在执行完成时传递回传的ACE给LocalFree。


说明

AllocateAce会传回一个PACE_UNION指标,它是本章先前定义的类型,我选择使用此技巧并以一般的方式表示ACEs。选择特定ACE类型之其中一个也是常见的方法,例如ACCESS_ ALLOWED_ACE,可使用在您的ACE之一般操作上。两种方法都行得通;然而,在本章的一些范例函数中,所使用的是PACE_UNION。


现在您已经建立了ACE,接下来您必须在对象现存的DACL中搜寻符合的ACEs。您可以经由使用本章先前谈论的ACL读取技巧来实作。在我编写执行此工作程序代码时使用了两个函数:一个用来对照ACEs,另一个则是在ACL中搜寻符合的ACE。

BOOL IsEqualACE(PACE_UNION pACE1,PACE_UNION pACE2 ){ 
	BOOL fReturn =FALSE;
	try{{
		if(pACE1->aceHeader.AceType !=pACE2->aceHeader.AceType)
			goto leave;
		//取得ACE中SID的偏移量
		ULONG lSIDOffset =(ULONG)(&((ACCESS_ALLOWED_ACE*)0)->SidStart);
		//取得没有SID的ACE大小
		ULONG lACEStructSize =sizeof(ACCESS_ALLOWED_ACE)-
			sizeof(((ACCESS_ALLOWED_ACE*)0)->SidStart);
		PBYTE pbACE1 =(PBYTE)pACE1;
		PBYTE pbACE2 =(PBYTE)pACE2;
		fReturn =TRUE;
		while(lACEStructSize--)
			fReturn =(fReturn &&((pbACE1 [lACEStructSize ] ===
				pbACE2 [lACEStructSize ])));
		/检查SIDs
		fReturn =fReturn &&EqualSid((PSID)(pbACE1+lSIDOffset),
			(PSID)(pbACE2+lSIDOffset));
	}leave:;
	}catch(...){
	}

	return (fReturn);
}

以下是第二个函数的内容:

int FindACEInACL(PACL pACL, PACE_UNION pACE ){ 
	int nACEIndex =-1;
	try{{
		ACL_SIZE_INFORMATION aclSize;
		if (!GetAclInformation(pACL, &aclSize, sizeof(aclSize),
			AclSizeInformation)){
			goto leave;
		}

		while (aclSize.AceCount--){
			PACE_UNION pACETemp;
			if(!GetAce(pACL, aclSize.AceCount, (PVOID *)&pACETemp))
				goto leave;
			if(IsEqualACE(pACETemp, pACE)){
				nACEIndex = (int)aclSize.AceCount;
				break;
			}
		}
	}leave:;
	}catch(...){
	}
	return (nACEIndex);
}

FindACEInACL函数会传回DACL中符合的ACE索引值,如果没有找到,则传回 -1值。您应该使用此函数或类似的函数,以找出您打算加到对象DACL的ACEs是否已经存在DACL中(FindACEInACL函数对于寻找您想要使用DeleteAce删除的ACEs也是很有帮助的)。

现在您已经知道哪些ACEs必须加入DACL及已建立的ACEs,现在是计算新DACL大小的时候了。您可以使用在本章前面出现过的CalculateACLSize函数。它可让您使用现存的ACL、SIDs阵列及ACEs阵列来计算ACL大小。您可以传递NULL给这些参数。在我们的范例中,您有可能会传递一个现存的ACL及ACEs阵列,以在加入ACEs后用来计算新ACL的大小。

在您分配内存给新的ACL后,应使用InitializeAcl(本章前面讨论过)来初始化新的ACL。接下来您必须从旧的ACL复制ACEs到新的ACL。

以下的函数显示其实行的方法:

BOOL CopyACL(PACL pACLDestination, PACL pACLSource ){ 
	BOOL fReturn = FALSE;
	try{{
		// 取得原始ACL中ACEs的数量
		ACL_SIZE_INFORMATION aclSize;
		if (!GetAclInformation(pACLSource, &aclSize,
			sizeof(aclSize), AclSizeInformation)){
			goto leave;
		}

		// 使用GetAce及AddAce复制ACEs
		for(ULONG lIndex=0;lIndex < aclSize.AceCount;lIndex++){
			ACE_HEADER* pACE;
			if(!GetAce(pACLSource, lIndex, (PVOID*)&pACE))
				goto leave;
			if(!AddAce(pACLDestination, ACL_REVISION, MAXDWORD,
				(PVOID*)pACE, pACE->AceSize))
				goto leave;
		}
		fReturn = TRUE;
	}leave:;
	}catch(...){
	}
	return (fReturn);
}

CopyACL函数相当简单,它只不过重复着通过现存DACL之ACEs并复制每一个到新DACL的动作而已。CopyACL使用了AddAce系统函数,其定义如下:

BOOL AddAce(
	PACL	pACL,
	DWORD	dwACERevision,
	DWORD	dwStartingACEIndex,
	PVOID	pACEList,
	DWORD	dwACEListLength);

请注意,AddAce与AddAccessAllowedAce及AddAccessDeniedAce不同。首先您必须提供ACE;系统不会为您收集ACE并放置到DACL里,这意味着您应要定义ACEs的类型,所以AddAce可以把任意类型的ACE加至DACL中;第二,经由使用以0开始的索引值,AddAce可让您决定想要插入新ACE到DACL的位置;第叁,AddAce可让您增加更多的ACE,经由传递连续的ACEs清单给pACEList参数以及清单大小给dwACEListLength参数(不要被清单中的ACEs数量混淆)。


说明

经由允许AddAce复制一个个别函数以呼叫之中所有的原始ACEs,CopyACL范例函数可以更有效地被实作。然而,如此做而取得的ACL结构资讯,应该被视为不透明的。


在您复制旧DACL到更宽敞的新DACL之后,可以使用AddAce开始加入ACEs。请注意,必须将您的ACEs插入适当的新DACL索引中,以便维护适当的ACE顺序(关于ACE顺序,请参阅 表10-6 )。您可以使用以下的函数为ACL中的新ACE决定适当的索引值:

ULONG GetACEInsertionIndex(PACL pDACL, PACE_UNION pACENew){ 
	ULONG lIndex = (ULONG)-1;
	try{{
		// ACL顺序的ACE类型
		ULONG lFilterType[] = {	ACCESS_DENIED_ACE_TYPE,
					ACCESS_DENIED_OBJECT_ACE_TYPE,
					ACCESS_ALLOWED_ACE_TYPE,
					ACCESS_ALLOWED_OBJECT_ACE_TYPE};

		// 决定新的ACE应该隶属的群组
		ULONG lNewAceGroup;
		for(lNewAceGroup = 0; lNewAceGroup<4 ; lNewAceGroup++){
			if(pACENew->aceHeader.AceType == lFilterType[lNewAceGroup])
				break;
		}
		// 假如群组 == 4,则这个ACE类型不好
		if(lNewAceGroup==4)
			goto leave;
		// 假如新的ACE是个继承的ACE,那么它会追求其他的ACEs
		if((pACENew->aceHeader.AceFlags & INHERITED_ACE) != 0)
			lNewAceGroup+=4;
		// 取得ACE总数
		ACL_SIZE_INFORMATION aclSize;
		if (!GetAclInformation(pDACL, &aclSize,
			sizeof(aclSize), AclSizeInformation)){
			goto leave;
		}

		// 重复通过ACEs
		lIndex = 0;
		for(lIndex = 0;lIndex < aclSize.AceCount;lIndex++){
			ACE_HEADER* pACE;
			if(!GetAce(pDACL, lIndex, (PVOID*)&pACE))
				goto leave;

			// 取得ACL之ACE的群组
			ULONG lAceGroup;
			for(lAceGroup = 0; lAceGroup<4 ; lAceGroup++){
				if(pACE->AceType == lFilterType[lAceGroup])
					break;
			}
			// 测试不好的ACE
			if(lAceGroup==4){
				lIndex = (ULONG)-1;
				goto leave;
			}
			// 调整继承
			if((pACE->AceFlags & INHERITED_ACE) != 0)
				lAceGroup+=4;

			// 假如是相同的群组,那么找出插入点
			if(lAceGroup>=lNewAceGroup)
				break;
		}

	}leave:;
	}catch(...){
	}
	return (lIndex);
}

GetACEInsertionIndex函数会找出您插入新ACE的索引值,并在过程中考虑对象ACEs及ACE继承的部份。在您知道索引值之后,可以呼叫AddAce,以增加新的ACE到ACL中。

每个新的ACE都会如此做,然后再使用适当的函数设定新的DACL到安全对象。不要忘了在您使用后清理并释放已经分配的任何内存。

在本章稍早,我曾答应要展示一个在DACL中安排ACEs顺序的范例函数。假如在您彻底完成加入ACEs到DACL前,一点也不想要担心ACE顺序的问题,那么此函数对您可能会很有帮助。以下的OrderDACL函数相当简单,它建立在GetACEInsertionIndex范例函数上:

BOOL OrderDACL(PACL pDACL ){ 
	BOOL fReturn = FALSE;
	try{{
	// 取得ACL大小及ACE总数
	ACL_SIZE_INFORMATION aclSize;
	if (!GetAclInformation(pDACL, &aclSize,
		sizeof(aclSize), AclSizeInformation)){
		goto leave;
	}
	// 为暂时的ACL取得内存
	PACL pTempDACL = (PACL)_alloca(aclSize.AclBytesInUse);
	if (pTempDACL==NULL)
		goto leave;
	// 初始暂时的ACL
	if (!InitializeAcl(pTempDACL, aclSize.AclBytesInUse,
		ACL_REVISION))
		goto leave;

	// 重复通过ACEs
	for (ULONG lAceIndex = 0;
		lAceIndex < aclSize.AceCount ; lAceIndex++){
		// 取得ACE
		PACE_UNION pACE;
		if (!GetAce(pDACL, lAceIndex, (PVOID*)&pACE))
			goto leave;
		// 找出位置,并且把ACE加入temp DACL
		ULONG lWhere = GetACEInsertionIndex(pTempDACL, pACE);
			if (!AddAce(pTempDACL, ACL_REVISION,
				lWhere, pACE, pACE->AceSize))
				goto leave;
		}
		// 复制temp DACL到原始的
		CopyMemory(pDACL, pTempDACL, aclSize.AclBytesInUse);
		fReturn = TRUE;
	}
	leave:;
	}catch(...){
	}
	return (fReturn);
}

让我们看看真实世界实作讨论过的技巧范例。

修改DACL范例
 

我们的范例提供了一组有用的函数给服务开发人员,它显示如何修改一个现存DACL的方法。我使用了Windows中的window站台及桌面安全对象。

 window站台 是Windows的安全对象,包含了剪贴簿、一组通用元素及桌面对象的集合。window站台可以是互动式的,意味着使用者可以看到它的「桌面」,一个互动式windows站台也包含键盘及滑鼠资讯。一个程序可以有一个与它相关联的个别windows站台。

 桌面 是一个包含在window站台内的安全对象。一个桌面维护着一个逻辑的显示外观,并包含功能表、视窗及萤幕上的其他可视对象。

不与使用者互动的服务和互动式桌面没有关联。当使用者登入系统时,互动式window站台(称为WinSta0)的DACL及其预设桌面(称为Default)会被重新设定,并且把此对象的存取权给予使用者。最后,只有已登录的使用者及系统会被授予对此对象的存取权。

 问题描述 服务有时必须在任一信任成员帐户下(使用下一章要讨论的CreateProcessAsUser函数)建立另一个程序,以在目前没有与系统互相作用的使用者环境下建立一个程序。假如此程序需要与使用者互动,而使用者帐户对互动式window站台及预设桌面没有存取权,则系统呼叫CreateProcessAsUser函数时会失败。

由于发现了这个问题,在建立程序之前,您必须检查这些对象之DACLs的使用者存取权利。假如没有找到这些权利,则它们必须被加入。首先检查权利是很重要的,因为盲目地增加ACE到使用者对象,最后可能会耗尽系统中的资源(通常您只能增加约80个ACEs到window站台)。

 解决方案 现在让我们开始着手处理解决方案。我使用这一节所讨论的工具及观念(和一些范例函数)实作了两个函数:一个可让信任成员存取window站台,而另一个可让信任成员存取桌面。这些函数非常简单。尽管修改对象DACL的程序看起来有点令人怯步,但真实世界中的程序并没有我们所想像的复杂。以下的程序代码可让信任成员存取window站台:

BOOL AllowAccessToWinSta(PSID psidTrustee, HWINSTA hWinSta ){ 
	BOOL fReturn = FALSE;
	PSECURITY_DESCRIPTOR psdWinSta = NULL;
	PACE_UNION pACENew = NULL;

	try{{
		// 取得window站台的DACL
		PACL pDACLWinSta;
		if(GetSecurityInfo(hWinSta, SE_WINDOW_OBJECT,
			DACL_SECURITY_INFORMATION, NULL, NULL, &pDACLWinSta,
			NULL, &psdWinSta) != ERROR_SUCCESS)
			goto leave;

		// 分派新的ACE
		// 这个存取授予互动地登录的使用者
		PACE_UNION pACENew = AllocateACE(ACCESS_ALLOWED_ACE_TYPE, 0,
			DELETE|WRITE_OWNER|WRITE_DAC|READ_CONTROL|
			WINSTA_ENUMDESKTOPS|WINSTA_READATTRIBUTES|
			WINSTA_ACCESSCLIPBOARD|WINSTA_CREATEDESKTOP|
			WINSTA_WRITEATTRIBUTES|WINSTA_ACCESSGLOBALATOMS|
			WINSTA_EXITWINDOWS|WINSTA_ENUMERATE|WINSTA_READSCREEN,
			psidTrustee);

		// ACE是否已经在DACL中?
		if (FindACEInACL(pDACLWinSta,pACENew) == -1){
			// 假如没有,计算新的DACL大小
			ULONG lNewACL = CalculateACLSize(pDACLWinSta, NULL, 0,
				&pACENew, 1 );
			// 分派内存给新的DACL
			PACL pNewDACL = (PACL)_alloca(lNewACL);
			if (pNewDACL == NULL)
				goto leave;
			// 初始ACL
			if (!InitializeAcl(pNewDACL, lNewACL, ACL_REVISION))
			goto leave;

			// 复制ACL
			if (!CopyACL(pNewDACL, pDACLWinSta))
			goto leave;

			// 取得新ACE的位置
			ULONG lIndex = GetACEInsertionIndex(pNewDACL, pACENew);

			// 增加新的ACE
			if (!AddAce(pNewDACL, ACL_REVISION, lIndex,
				pACENew, pACENew->aceHeader.AceSize))
				goto leave;

			// 设定DACL回到window站台
			if (SetSecurityInfo(hWinSta, SE_WINDOW_OBJECT,
				DACL_SECURITY_INFORMATION, NULL, NULL,

				pNewDACL, NULL)!=ERROR_SUCCESS)
				goto leave;
		}
		fReturn = TRUE;
	}leave:;
	}catch(...){
	}
	// 清除
	if(pACENew != NULL)
		LocalFree(pACENew);
	if(psdWinSta != NULL)
		LocalFree(psdWinSta);
	return (fReturn);
}

下一个范例函数可让信任成员存取桌面:

BOOL AllowAccessToDesktop( PSID psidTrustee, HDESK hDesk ){ 
	BOOL fReturn = FALSE;
	PSECURITY_DESCRIPTOR psdDesk = NULL;
	PACE_UNION pACENew = NULL;

	try{{
		// 取得桌面的DACL
		PACL pDACLDesk;
		if(GetSecurityInfo(hDesk, SE_WINDOW_OBJECT,
			DACL_SECURITY_INFORMATION, NULL, NULL, &pDACLDesk,
			NULL, &psdDesk) != ERROR_SUCCESS)
			goto leave;

		// 分派新的ACE
		// 这个存取授予互动地登录的使用者
		PACE_UNION pACENew = AllocateACE(ACCESS_ALLOWED_ACE_TYPE, 0,
			DELETE|WRITE_OWNER|WRITE_DAC|READ_CONTROL|
			DESKTOP_READOBJECTS|DESKTOP_CREATEWINDOW|
			DESKTOP_CREATEMENU|DESKTOP_HOOKCONTROL|
			DESKTOP_JOURNALRECORD|DESKTOP_JOURNALPLAYBACK|
			DESKTOP_ENUMERATE|DESKTOP_WRITEOBJECTS|DESKTOP_SWITCHDESKTOP,
			psidTrustee);

		// ACE是否已经在DACL中?
		if (FindACEInACL(pDACLDesk, pACENew) == -1){
			// 假如没有,计算新的DACL大小
			ULONG lNewACL = CalculateACLSize(pDACLDesk, NULL, 0,
				&pACENew, 1 );
			// 分配内存给新的DACL
			PACL pNewDACL = (PACL)_alloca(lNewACL);
			if (pNewDACL == NULL)
				goto leave;
			// 初始ACL
			if (!InitializeAcl(pNewDACL, lNewACL, ACL_REVISION))
				goto leave;
			// 复制ACL
			if (!CopyACL(pNewDACL, pDACLDesk))
				goto leave;
			// 取得新ACE的位置
			ULONG lIndex = GetACEInsertionIndex(pNewDACL, pACENew);


			// 增加新的ACE
			if (!AddAce(pNewDACL, ACL_REVISION, lIndex,
				pACENew, pACENew->aceHeader.AceSize))
				goto leave;

			// 设定DACL回到桌面
			if (SetSecurityInfo(hDesk, SE_WINDOW_OBJECT,
				DACL_SECURITY_INFORMATION, NULL, NULL,
				pNewDACL, NULL)!=ERROR_SUCCESS)
				goto leave;
		}
		fReturn = TRUE;
	}leave:;
	}catch(...){
	}
	// 清除
	if(pACENew != NULL)
	LocalFree(pACENew);
	if(psdDesk != NULL)
		LocalFree(psdDesk);
	return (fReturn);
}

以下的程序片段显示如何使用这些函数的范例。此程序代码为内建的Everyone群组建立了一个SID,并将它传递到AllowAccessToWinSta及AllowAccessToDesktop函数中:

PSID psidEveryone; 
// 为内建的「Everyone」群组建立一个SID
SID_IDENTIFIER_AUTHORITY sidAuth = SECURITY_WORLD_SID_AUTHORITY;
if (!AllocateAndInitializeSid(&sidAuth, 1, SECURITY_WORLD_RID,
	0, 0, 0, 0, 0, 0, 0, &psidEveryone )){
	// 错误
}

HWINSTA hWinSta = GetProcessWindowStation();
if (hWinSta == NULL){
	// 错误
}
AllowAccessToWinSta(psidEveryone, hWinSta);
HDESK hDesk = GetThreadDesktop(GetCurrentThreadId());
if (hDesk == NULL){
	// 错误
}
AllowAccessToDesktop(psidEveryone, hDesk);

说明

这里有个秘诀。AllowAccessToWinSta及AllowAccessToDesktop函数使用了标头文件Malloc.h中的C执行时期(Run-Time)程序库定义的_alloca函数,_alloca函数会在线程的堆叠上分配一块内存。此函数的优点是非常快速,不需内部的线程同步化及传回不必由应用程序释放的内存。当您跳出被呼叫的函数时,系统就会释放内存。 对于必须重复小型配置的安全性程序设计师而言,这样的函数会是个救命的工具,它将会加快程序代码的速度并帮助您避免内存缺乏的情形。


在此强烈地建议您找出时间察看AllowAccessToWinSta及AllowAccessToDesktop函数的内容,直到您了解它们的运作方式并有自信使用它们所采用的技巧为止。这些范例函数所执行的任务大约和您在存取控制程序设计中看到的一样复杂,假如您对它们感到自在,则实作符合您所需的存取控制大概就不会有困难。

实作存取控制的选择
 

Microsoft及其他厂商试图经由建立更高阶的函数来封装我们曾讨论过的低阶函数,以减轻存取控制程序设计师的重担。协力厂商也已经产生了减轻存取控制工作的解决方案。认识Windows之低阶存取控制的功能以及这些高阶函数的某些陷阱,应该能够让您做出满足程序代码需求的决定。

更高阶套件(Package)的实作器已经面临到这些挑战:

  • 简化非常有弹性的存取控制系统,不用限制弹性或在专案中可能需要的特色。
     
  • 建立健全且合用的程序代码。
     

第一个挑战-即维持弹性-是最难克服的。大多数的存取控制需求,可以使用Windows存取控制直接实作。然而,随着弹性而产生的复杂性,以及移除复杂性时就会移除了某些开发人员所期待的特色。

看起来第二个挑战似乎比较可能达成,但是这个实例还未被证明。Microsoft实作了一组「高阶」安全性的函数,并且已加入Win32 API中。这些函数彻底地简化了某些安全性程序设计的观点,以及某些具有历史性的错误及固有的缺点。以下举出一个范例:

DWORD GetEffectiveRightsFromAcl(
	PACL	pACL,
	PTRUSTEE	pTrustee,
	PACCESS_MASK	pAccessRights);

GetEffectiveRightsFromAcl函数为了搜寻ACL及传回DACL指出允许之存取权利到信任成员的存取遮罩而准备。听起来非常方便!这样的函数可以潜在中将我们的需求移到加入ACEs之前,先在DACL中搜寻ACEs。然而,GetEffective RightsFromAcl试图做太多的事,如些一来,会产生做出来的成果几乎不能使用的情形。

GetEffectiveRightsFromAcl函数指出允许存取权是以一致的允许存取ACEs之组合为基础,然后再减去一致的拒绝存取ACEs的组合。这意味着GetEffective RightsFromAcl可以传回一组存取权利,它会指出ACL没有授予想要的存取权给信任成员部份,在我离开时仍旧不知道如何处理这个问题。没有授予存取权是因为缺少允许存取ACEs,或是因为存在拒绝存取ACEs呢?我厌恶增加允许存取ACE的方法,只想找出无效拒绝存取ACE使我仍然没有存取权的原因。

GetEffectiveRightsFromAcl不只会搜寻符合您提供的信任成员ACEs,也搜寻信任成员所属之群组帐户的任何ACEs。但是这个函数并没有包括内建的群组,例如Everyone及受认证的使用者。假如它需要寻找群组的ACEs,而信任您程序代码的群组成员并没有列举权利时,此动作会失败。最后,没有方法可以限制搜寻程序只找出明确指派给信任成员的存取权。

GetEffectiveRightsFromAcl是为了使您的程序代码能够查出信任成员对象上的存取检查是否成功或失败而准备。然而,存取检查需要一个权杖而非信任成员SID。权杖包含权限;权杖也可以被调整或限制(请参阅 第十一章 )。在检查存取时,GetEffectiveRightsFromAcl不会把权限带到帐户里。存取检查能够执行成功是以对象所有权为基础,但是GetEffectiveRightsFromAcl并不了解对象所有权。这些方法使得GetEffectiveRightsFromAcl的使用有限。

更有帮助的函数SetEntriesInAcl及GetExplicitEntriesFromAcl是为了帮助您在卸下为ACEs及ACLs分配的内存责任的同时,也可以直接处理ACL而准备。这些函数的目的很重要,但是这个函数具有历史性的错误及效能上的争议。某些问题已经被清除了,不过如果您选择在专案中使用这些函数,彻底测试其程序代码是很重要的。

应该与SetEntriesInAcl一起使用的BuildExplicitAccessWithName函数不会传回任何值,它会潜在地把错误方案(当使用低阶函数呼叫LookupAccountName时,可以撷取)委托给SetEntriesInAcl函数。由于SetEntriesInAcl没有回报失败项目的方法,因此您会因为无法从失败案例中恢复而离开。

您可以有其他的选择。Active Template Library(ATL)的开发人员设计了一个称为CSecurityDescriptor的C++ 类别,定义在标头档AtlCom.h中。CSecurityDescriptor之额外的好处是提供全部的类别原始码。尽管此类别提供了大量的功能,但本身也有陷阱。例如,此函数会以一向不遵守Windows 2000提出的ACE顺序的方式增加ACEs,虽然它们遵守Windows NT 4.0的方针。此外,从核心对象撷取安全性的AttachObject函数则用GetKernelObjectSecurity代替GetSecurityInfo,它是与Windows 2000一起使用所建议的函数。

如前所述,CSecurityDescriptor类别最好的东西即是拥有原始码。假如它朝您需要的方向去运作,那会很好!如果没有的话,您便可以选择修改类别。而且若您找到任何错误,可以选择使用您拥有与Windows之低阶存取控制相关的知识来修改它们。

安全的私有对象
 

我已经提过好几次,您可以经由使用Windows存取控制来保护软件建立的私有对象。这个特色真的很强大,尤其是它可能对服务开发人员有用。到目前为止您已经学习的DACL建立技巧,适用于保护私人对象和系统对象,所以在您自己的对象上实作存取控制所需学习的部份并不多。

这个工作牵涉到需要保护的对象在您的软件及系统间共用的情形。您的软件必须执行以下的工作,以实作对象的私有安全性:

  • 您的软件必须为您的对象定义特定的权利(使用存取遮罩的低16位元)。
     
  • 您的软件必须决定哪个标准权利与您的对象有关。
     
  • 您的软件必须决定通用权利对应到哪个标准及特定权利。
     
  • 您的软件必须把安全描述项(系统建立的)与对象联系在一起。
     
  • 您的软件必须将适合您对象的安全描述项与对象储存到永续性储存体中。
     
  • 您的软件在安全对象上执行安全性活动前,必须先执行存取检查。
     

系统提供了以下的特色:

  • 为您的对象建立及删除安全描述项的函数。
     
  • 取得及设定这些安全描述项之特殊部分函数。
     
  • 在某方面来说,一个执行存取检查的函数会与Windows之其他安全对象一致。
     

在我们开始讨论特定函数前,必须阐明以下两点:

  • 私人对象安全性应该能被不同安全性环境中执行服务之客户端服务软件使用。
     
  • 私人对象安全性要求您的软件使用权杖指出客户端之安全性环境。请记得权杖包含了一个信任帐户的SID与它的群组SIDs、权限及预设DACL(第十一章将会更详细讨论权杖的内容)。
     

建立一个私下被保护的对象时,您应该建立对象的安全描述项。可呼叫CreatePrivateObjectSecurity函数实作:

BOOL CreatePrivateObjectSecurity(
	PSECURITY_DESCRIPTOR	psdParentDescriptor,
	PSECURITY_DESCRIPTOR	psdCreatorDescriptor,
	PSECURITY_DESCRIPTOR	*ppsdNewDescriptor,
	BOOL	fIsDirectoryObject,
	HANDLE	hToken,
	PGENERIC_MAPPING	gmGenericMapping);

CreatePrivateObjectSecurity函数有一个非必要的父安全描述项与建立者描述项,这两个参数都可以为NULL。假如两者都没有提供的话,CreatePrivateObjectSecurity函数会透过所提供之权杖中找到的预设DACL而建立一个安全描述项。在预设DACL中找到的通用权利,可使用从被传递之GENERIC_MAPPING结构中找到的资讯,并将它们对应到对象的特定及标准权利。CreatePrivateObjectSecurity函数经由指派它的位址给PSECURITY_DESCRIPTOR变数,以传回新的安全描述项,您所传递的位址为ppsdNewDescriptor参数值。这是一个新的安全描述项,现在您应该把它与您的私有安全对象联系在一起。当您需要释放此函数所分配的内存时,应该传递指向想要释放之安全描述项的变数位址给DestroyPrivateObjectSecurity。

BOOL DestroyPrivateObjectSecurity(
	PSECURITY_DESCRIPTOR *ppsd ObjectDescriptor );

被您初始化及传递给CreatePrivateObjectSecurity的GENERIC_MAPPING结构定义如下:

typedef struct _GENERIC_MAPPING {
	ACCESS_MASK GenericRead;
	ACCESS_MASK GenericWrite;
	ACCESS_MASK GenericExecute;
	ACCESS_MASK GenericAll;
}GENERIC_MAPPING;

您可经由将每个成员设定到每个通用权利之适当的标准及特定权利组合,以填写这个简单的结构。从权杖的预设DACL建立DACL时,系统会使用这个资讯。


说明

您不应直接修改从CreatePrivateObjectSecurity传回的安全描述项。尽管系统提供从此描述项要求特定安全性资讯的函数,您的软件应该把这个传回的指标视为黑箱,犹如安全描述项被储存在系统内存一般。


当客户端试图对软件中的安全对象作用时,首先客户端必须接受存取检查。为此,您应传递执行安全工作所需的权利、安全描述项及客户端的权杖到AccessCheck函数中。

BOOL AccessCheck(
	PSECURITY_DESCRIPTOR	pSecurityDescriptor,
	HANDLE	hClientToken,
	DWORD	dwDesiredAccess,
	PGENERIC_MAPPING	gmGenericMapping,
	PPRIVILEGE_SET	pPrivilegeSet,
	PDWORD	pdwPrivilegeSetLength,
	PDWORD	pdwGrantedAccess,
	PBOOL	pfAccessStatus);

您也必须包含此对象的GENERIC_MAPPING结构位址以及PRIVILEGE_SET结构阵列的位址。系统使用PRIVILEGE_SET结构来描述用来授予存取的权限。系统以几个实例来授予使用权限的存取权,而您应该提供够大的缓冲器以接收数个回传的权限。PRIVILEGE_SET结构定义如下:

typedef struct _PRIVILEGE_SET {
	DWORD	PrivilegeCount;
	DWORD	Control;
	LUID_AND_ATTRIBUTES	Privilege [ANYSIZE_ARRAY];
}PRIVILEGE_SET;

ANYSIZE_ARRAY值被定义为1,而您应该建立一个够大的缓冲器以接收有关的权限。一旦收到权限缓冲器要求的大小后,您可以呼叫AccessCheck,然后再配置一个够大的缓冲器。


说明

当您的客户端要求对象的WRITE_OWNER、READ_CONTROL、WRITE_DAC或ACCESS_SYSTEM_SECURITY的存取时,该权限可以被用来授予存取。假如这些权利没有明确地指派给您客户端的信任成员帐户,系统会为覆盖对象安全性的权限检查客户端的权杖。这种权限的一个例子为SE_TAKE_OWNERSHIP_NAME,它可让客户端设定系统中任何对象的拥有权。


您的客户端被授予的存取遮罩会经由pdwGrantedAccess参数传回,而经由pfAccessStatus参数传回的Boolean值会指出AccessCheck函数是否成功。

在实作私有对象之安全性时,AccessCheck函数是其中心,假如您的软件是安全的,而它一定会在对象执行安全工作前呼叫AccessCheck,则当您软件的客户端被允许存取时,Windows会处理剩下的细节部份。


说明

您也可以使用AccessCheckAndAuditAlarm函数建立稽核事件及私有安全对象。稽核的内容将在本章稍后讨论。


到此为止,您已经知道如何建立及删除私有安全性的方法,您也知道如何及应在何时呼叫AccessCheck函数。所以私有对象主题唯一剩下的部分即是如何修改安全描述项的元件。为了修改私人对象的安全性,首先应该取得您要调整的安全描述项元件。这通常是DACL,它可以是对象的拥有者或SACL。可使用GetPrivateObjectSecurity实作。

BOOL GetPrivateObjectSecurity(
	PSECURITY_DESCRIPTOR	psdObjectDescriptor,
	SECURITY_INFORMATION	secInfo,
	PSECURITY_DESCRIPTOR	psdResultantDescriptor,
	DWORD	dwDescriptorLength,
	PDWORD	pdwReturnLength);

GetPrivateObjectSecurity函数取得一个指向私有安全对象的安全描述项指标,并传回所要求的安全描述项。您可以使用熟悉的secInfo参数以指出您想撷取对象安全描述项的哪个部分。

您必须提供一个够大的缓冲器来包含持有要求资讯的安全描述项。一旦得到要求的缓冲器大小时,便可以呼叫GetPrivateObjectSecurity函数,并且再一次撷取资讯。

在您拥有对象的安全描述项后,必须修改它,然后再使用SetPrivateObjectSecurity函数重新设定修改过的安全描述项给私有对象。

BOOL SetPrivateObjectSecurity{
	SECURITY_INFORMATION	secInfo,
	PSECURITY_DESCRIPTOR	psdModificationDescriptor,
	PSECURITY_DESCRIPTOR	*ppsdObjectsSecurityDescriptor,
	PGENERIC_MAPPING		gmGenericMapping,
	HANDLE 		hClientToken);

secInfo参数指出「修改安全描述项」中的哪些资讯已被设定到对象的私有安全描述项中。您可以传递PSECURITY_DESCRIPTOR变数的位址,它包含一个指向对象私有安全描述项的指标。SetPrivateObjectSecurity函数会释放安全描述项,并以新的安全描述项取代它,其位址从提供的指标变数中传回。您也必须传递hClientToken参数,以使系统可以确认对象拥有者的设定,及其通用的对应结构。

安全描述项的修改通常会使用以下这些步骤执行:

  1. 撷取对象的安全描述项。
  2. 建立及初始新的安全描述项(本章稍早曾示范)。
  3. 从原始的安全描述项复制安全性资讯到新的安全描述项。
  4. 修改新的安全描述项(使用遍及本章所叙述的技巧)。
  5. 把新的安全描述项放回私人对象。

为了取得安全描述项的个别元件,您应该使用以下的函数:即GetSecurityDescriptorOwner、GetSecurityDescriptorDacl、GetSecurityDescriptorSacl及GetSecurityDescriptorGroup。为了设定新安全描述项的内部元件,您可以使用SetSecurityDescriptorOwner、SetSecurityDescriptorDacl、SetSecurity DescriptorSacl、SetSecurityDescriptorGroup来设定。

这些函数都很相似,所以我准备展示及讨论最常见与复杂GetSecurityDescriptorDacl及SetSecurityDescriptorDacl函数。

以下是GetSecurityDescriptorDacl函数原型:

BOOL GetSecurityDescriptorDacl(
	PSECURITY_DESCRIPTOR	pSecurityDescriptor,
	PBOOL	pfDACLPresent,
	PACL	*pDACL, 
	PBOOL	pfDACLDefaulted);

这个函数撷取一个指向安全描述项的DACL指标,也指出DACL是否为当前的,以及它起初是否已透过预设安全性建立。您所提供之安全描述项与Boolean值的位址会指出DACL是否为当前的,而PACL变数的位址则用来撷取DACL;另一个Boolean值位址,系统用它来指出DACL是否被设为预设值。

为了将DACL放回安全描述项,您可以使用SetSecurityDescriptorDacl函数:

BOOL SetSecurityDescriptorDacl(
	PSECURITY_DESCRIPTOR	pSecurityDescriptor,
	BOOL	bDaclPresent,
	PACL	pDacl,
	BOOL	bDaclDefaulted);

这个函数仅取得指向您想修改之安全描述项对象的指标,而Boolean值则指出DACL是否为当前的,以及它是否为预设安全性的结果(您通常会传递FALSE值)。SetSecurityDescriptorDacl函数要求一个指向DACL的指标,这个DACL会成为安全描述项的新DACL。


说明

您不该试图设定DACL或任何其他系统之任何对象传回的安全描述项元件。您应该总是建立一个新的安全描述项,并对它设定DACL及SACL元件。这是由于系统传回的安全描述项被封装至selfrelative格式的缘故。这意味着进入单一连续内存区块的资料,并没有留下可修改的空间。


当您使用InitializeSecurityDescriptor函数分配内存及初始化一个新的安全描述项时,系统会初始一个absolute格式的安全描述项。一个absolute格式的安全描述项使用了指标,以参考其他的元件,它可让元件被设定及重新设定。

RoboService范例服务程序
 

RoboService范例服务程序(「10 RoboService.exe」)示范如何利用私有安全对象及主从架构存取控制使用命名管道的方法。此应用程序的原始码及文件存放于随书光碟上的10-RoboService目录中。

当您使用「/install」参数启动RoboService时,应用程序会把它自己当成服务,并安装在系统上(如图10-8),然后您就可以使用任何服务控制程序启动及停止服务。同样地,如果您正在执行除错工具的服务程序,则您可以传递「/debug」参数,使服务在除错模式下执行,并绕过服务功能。


 

 图10-8 RoboService的命令列选项

在执行服务后,您可以执行RoboClient范例应用程序,它的执行画面显示于图10-9,然后再输入执行服务的系统电脑名称。假如您不输入电脑名称的话,客户端会试图连接本机上的服务。

一旦连接成功,您将会看到服务所建立的「虚拟机器」清单。您可以增加及移除机器。使用服务端的私有安全功能保护这些机器。您可以让机器执行几个已定义的行为,包括编辑机器的安全性等等。


 

 图10-9 RoboClient范例应用程序的使用者介面

建议您执行这个服务并从多重使用者环境下启动客户端程序,或许可以使用封装在Windows内的RunAs.exe公用程序执行。这可让您实验对象所有权及存取控制的部份。

客户端程序非常小,几乎已经把所有的功能皆委托给服务了。所有安全性的程序代码(除了ACL编辑外)皆在服务端实作。

服务使用 模拟(impersonation) 以取得每个连接客户端的权杖(关于模拟的内容在第十一章有更详细的讨论),然后服务会为来自客户端之未来的安全性要求,以及在机器上建立的使用权储存权杖。服务使用了CreatePrivate ObjectSecurity、DestroyPrivateObjectSecurity及AccessCheck,以及许多与安全性相关的函数来实作。

除了安全性功能以外,服务使用了第二章所讨论的I/O完成连接埠,有效地与客户端通讯,并为可调整的通讯实作了一个模组。

稽核与SACL
 

最后,我们准备开始讨论稽核和SACL的内容。与DACL不同的是,对象的SACL不影响可存取对象的人。

当对象被要求存取时,SACL会产生一个被加入事件日志的事件。特别的是,您可以增加ACEs到对象的SACL中,两种情况下会增加事件到事件日志中:

  • 当任何一组存取权利的存取检查成功时。
     
  • 当任何一组存取权利的存取检查失败时。
     

举例来说,您可以为NTFS分割区上包含个别SYSTEM_AUDIT_ACE的文件建立一个SACL。这表示若发生因为拒绝存取而使每次某个信任成员写入文件失败的情形时,应该由系统将此事件加到事件日志中。

Windows 2000的预设稽核选项是失效的,所以如果您想要开始编写稽核软件,应该加入稽核的能力。以下即是赋予稽核至系统的步骤:

  1. 使用 /a参数mmc /a,执行Microsoft管理主控台(MMC)。
  2. 在主控台功能表上,选择新增/移除嵌入式管理单元,然后点选新增按钮。
  3. 在新增独立嵌入式管理单元对话方块中,选择群组原则,然后点选新增按钮。
  4. 在选取群组对象的对话方块中,应选取本机电脑,然后点选完成按钮。
  5. 点选关闭按钮,然后再点选确定钮。此时本机电脑原则对象应该被显示在MMC中。
  6. 以下展示本机电脑的原则对象:本机电脑原则/电脑设定/Windows设定/安全性设定/本机原则/稽核原则。
  7. 从右边窗格的稽核对象存取上点滑鼠右键,然后选择安全性钮。
  8. 在本机安全性原则设定对话方块中,如图10-10所示,核取成功及失败核取方块,然后点选确定钮。


 

 图10-10 授予成功及失败稽核给对象存取

在采用这些步骤之后,系统会开始增加稽核事件到安全性日志下的事件日志。


说明

网域原则可以覆盖您的本机原则,并阻止稽核被设定。您必须有网域管理的权利才能调整网域原则。


相不相信,您已经完成实作稽核最难的工作。您知道如何从我们处理DACLs的工作中建立ACEs及修改ACLs。目前为止,您已经学会应用于建立ACEs及SACLs的每件事。

以下是SACL使用的ACE结构:

typedef struct _SYSTEM_AUDIT_ACE {
	ACE_HEADER	Header;
	ACCESS_MASK	Mask;
	DWORD	SidStart;
}SYSTEM_AUDIT_ACE;

这个结构看起来应该很熟悉,因为它与我们已经讨论过的ACCESS_ALLOWED_ACE及ACCESS_DENIED_ACE结构完全一样。ACE_HEADER结构的AceFlags成员通常会包含继承资讯,稽核ACEs也确实为真。而您也该含入SUCCESSFUL_ACCESS_ACE_FLAG及FAILED_ACCESS_ACE_FLAG稽核标记的其中一或两个,并分别指出成功及失败的存取稽核。

为了修改对象的SACLs,可以使用您已经熟悉的函数,例如InitializeAcl及AddAce。为仍旧使用GetSecurityInfo及SetSecurityInfo处理的系统对象取得及设定SACL使用几乎相同的方式,您可以将它们使用在DACL上。建立一个SACL比建立DACL稍微简单,由于SACL中的ACEs顺序在对象上没有影响,因此您可以用任何方便的顺序增加ACEs。


说明

处理DACLs时要注意一个差异点,那就是信任成员不能设定对象的SACL,除非它拥有SE_AUDIT_NAME权限。


设计存取控制程序应考虑的因素
 

您现在已经了解什么样的存取控制是合适的,以及如何在您自己的软件中实作存取控制,然而您还要学习更多,以成为一个成功的安全性程序设计师-Windows存取控制的弹性使安全性开发人员因为太多规则而绑住了自己。本节会讨论一些在做安全性程序开发时应考虑的议题,在此对您的劝告是尽早计划您服务的存取控制,提前做计划会替您省去大量的麻烦。

内存管理
 

和在百分之九十的应用层程序设计不同的是,安全性程序设计需要大量的缓冲器管理机制,包括分配及移动内存内的结构。尽管这是许多开发人员在学校专注的部分,但它并不是Windows开发中常见的工作。

在处理安全性时,您应该提供简化内存管理的方法,因为有很多函数皆要求分配及重分配暂时缓冲器。以下是一些秘诀:

  • 考虑使用不需要释放内存的函数,例如_alloca。
     
  • 考虑编写一个类别,例如CautoBuf类别,此类别在附录B中讨论(您也会在附录B中找到范例应用程序),这个类别会自动稽核它的缓冲器,并在它离开范围时释放它自己。
     
  • 避免使用static缓冲器;它们是懒惰程序设计师的技巧,最后将会破坏您的应用程序。
     
  • 考虑手边的工作,并设计一个尽可能要求少数缓冲器分配的方法。尽可能在开始演算时放置大量的缓冲器分配机制。
     
  • 在您使用后利用结构化的例外处理清理它。
     

重复利用的程序代码
 

编写越多的程序代码,就会有越多的错误。就存取控制程序来说,这绝对是真的。试着以服务的不同观点来设计彼此一致的安全性,以使您依据某些样式及重复使用函数,这种函数越多越好。重复使用的机制会大大地减轻侦错程序代码的工作。

最后,我无法充分的表达出用一组小的C++ 类别实作您的安全性程序代码是多么有利,这么做会使您换取重复使用的程序代码及封装(Encapsulation),并且可以大大地简化存取控制这类的工作。

保持简单
 

简单的安全性往往是最好的安全性。假如一个对象拥有许多ACEs,您的软件及使用者要监视确实有存取对象的存取时可能会有困难。假如对象只有一些简单的ACEs,则软件和使用者会很清楚谁做了存取的动作。一个被系统拥有且只有一个空的DACL对象(不是NULL DACL)和从Windows中取得的对象一样简单及安全。增加一个个别的ACE可让信任成员存取及对象仍旧是安全的,并且可以很清楚谁拥有对象的存取权。

试着只使用允许存取的ACEs来实作大量的存取控制。请记得除非明确地允许存取权,否则即表示它被暗示性地拒绝。若您使用允许存取ACEs及拒绝存取ACEs时,即表示允许群组及个人的存取权以及拒绝个人及较小群组的存取权。这样一来,您将会发现自己不必为群组中的个别使用者覆盖群组的拒绝存取ACE。所以尽量使它保持简单以避免存取控制产生漏洞。

使用预设安全性及继承的安全性
 

假如有可能的话,请使用预设安全性及继承的安全性。假如您的服务可以避免在个别对象上操作ACLs,这样会很好!许多服务可以只建立个别的DACL并将它设定为服务的预设DACL(第十二章有更详细的讨论)。

由服务建立的对象可以使用预设安全性,以避免为每个建立的对象产生一个DACL的需求。这个方法可以大大地简化必须建立许多安全对象的服务。


说明

继承安全性的对象不使用来自预设DACL的ACEs。您会发现如果要使用子对象的预设安全性时,必须保护父目录或登录机码的安全描述项。


继承的安全性是另一个取得免费DACL的方法。您可以设定文件结构(或登录树)中个别父对象上的DACL,在节点下建立的每个对象皆可以继承全部DACL的ACEs。这是一个非常有效的技巧,它可以使您的程序代码免于建立大量的个别DACLs。


说明

不可继承ACEs的这些对象本身即可以拥有可继承的ACEs。所以您可以建立一个受保护的根目录或登录机码,以避免使用继承的ACEs,而对象的ACEs可以为下面的每个对象定义继承的安全性。这是一个在文件及登录机码上实作继承安全性之常见且有效的策略。

  第十一章 )。执行后产生如表10-15的内容。 图10-2 。

 表10-7 安全对象的特定安全函数

尽管表10-7中每个对象的建立对象函数不同,只有少数的函数被要求设定及取得系统中所有安全对象类型的安全性资讯。


说明

有为特定对象类型取得及设定安全性的其他函数,例如GetFileSecurity及SetKernelObjectSecurity。然而,由于GetFileSecurity及SetKernelObjectSecurity等函数变得更容易使用,而且在Windows 2000中提供了更多完整的继承模组实作对象,使得特定函数不再是取得及设定对象安全性的惯用方式。可能的话,您应该使用这些函数。


读取对象的安全性资讯
 

只有 读取 对象的安全性资讯不为常见的任务,通常您会执行它,如此您就可以使用某些方法去修改安全性。然而,了解如何读取对象的安全性会大大地简化更多修改对象安全性的常见工作,所以我们在这里讨论这个主题。

您可能会猜想到并非每个人都可以读取对象的安全性。就像其他在安全对象上执行的任务一般,读取其本身安全性资讯的能力是安全的。为了要达成读取安全性资讯的目的,以下所列条件的其中一或两个必须为真:

  1. 您是这个对象的拥有者。
  2. 对象之DACL中的ACE或者您所属的群组,授予您READ_CONTROL标准权利(请参阅 

 图10-2 存取遮罩格式

ACEs指出了存在于父系—子系关系层级中的系统及私人对象之继承的规则。当我们开始有纲领的讨论ACLs操作时,我将会涵盖更详细的继承内容;然而,现在了解被允许的继承类型是有帮助的。以下的列表说明了这些可能性:

  • 一个包含ACE的父对象不会影响到上层,但会影响子对象。相反地,ACE对父对象及子对象的影响是有可能的。
     
  • 拥有ACE的父系会影响子对象,但不会继续让Grandchild、Great-grandchild等对象继承。您也可以指定一个无限继承的ACE。
     
  • 一个让子(或Grandchild)对象继承的ACE,同时也是一个能影响容器对象的容器,或者它可以被设定成只影响非容器的子对象。
     
  • ACE可以被定义为只被同为容器的子对象继承,或者被任何子对象继承。
     

这些决策方式的每一个都可以组成ACE,不管ACE的其他继承属性。所以如您所见,ACE可以用几种不同的方法继承。


说明

建立一个不允许从父系继承ACEs的对象安全描述项是可能的,这样的描述项称为 保护的安全描述项 ,它阻止对象和子对象的继承,不过却不影响父对象或任何其他父系子成员的安全描述项。

系统记录了哪个ACEs被继承及明确地指定给对象的内容。如此一来,如果安全描述项在对象被建立后即被设定为保护的安全描述项,那些系统会知道要从DACL及SACL中移除哪个ACEs。并且会明确指定对象的ACEs优先于被继承的ACEs。


存取检查
 

您现在应该对如何维持一个安全对象的防护有了概念。我们将很快地开始讨论安全API的部份,但是您必须先了解存取检查的内容。

当您登录执行Windows 2000的系统时,会输入您的使用者名称。系统会寻找您所属的成员使用者帐户及群组信任成员帐户,并储存每个信任成员帐户的SID到一个称为 权杖(Token) 的内部结构中。系统也储存指派给您的信任成员及群组信任成员帐户权限清单。您将在第十一章中学到更多关于权杖的内容,但您现在应该将权杖视为一个储存身分及权限的结构。

系统为您建立权杖及启动Shell处理后,会将您的权杖与Shell联系在一起。从此之后,Shell执行的任何处理程序皆会自动启动继承您的权杖副本。这就是系统维护身分识别的观念。

与您权杖相关的处理程序在您的使用者环境或安全性环境中执行。当安全性环境执行的程序试图在安全对象上执行安全动作时,系统首先会执行存取检查,以确定您或其中一个群组是否有足够的权利去执行这个任务。图10-3显示了包含在存取检查中的实体关系。


 

 图10-3 存取安全对象的程序

说明

程序中的线程也有可能在使用者环境中执行,这与程序不同,称作 模拟 ,详细说明涵盖在下一章。


以下为系统执行存取检查时采取的步骤:

  1. 系统对应要求标准通用权利及特殊权利。
  2. 系统为这个存取检查中有关的权限检查您的权杖。大部分的存取检查不考虑权限。然而,如果您持有SeTakeOwnershipPrivilege权限,系统会永远通过WRITE_OWNER标准权利的存取检查(请参阅 
  3. 系统将您权杖中的SID及群组SIDs与对象拥有者的SID作比较。假如您是对象的拥有者,而且要求了READ_CONTROL或WRITE_DAC的标准存取权利,则系统不会管DACL的内容即准予存取。
  4. 系统检查安全描述项中的DACL实体。假如不是当前的,则准予存取。
  5. 系统对照您权杖中的SIDs,并检查DACL之第一个ACE的SID。假如找到符合的内容,则对照存取检查所要求的存取权利而检查ACE的存取遮罩。
  6. 假如ACE是拒绝存取的ACE,且存取遮罩与任一个要求的存取权利相符,则存取检查会立即失败。
  7. 假如ACE是允许存取的ACE,并且满足任何或所有存取检查要求的存取权利,系统会把执行成功事件记录下来。
  8. 存取检查要求的所有权利都符合后,系统就通过存取检查。
  9. 假如DACL中最后一个ACE被检查,而且没有找到检查要求的所有权利,则系统的存取检查失败。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值