Windows2000 服务器端应用程序开发设计指南-存取控制(7)
转自:http://mickorguo.blog.163.com/blog/static/6219480200741221325431/
在我们继续前,必须再提一个预设安全性的重点。假设您要处理的对象,其安全性是继承而来的(例如登录机码或文件),则预设安全性只适用于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时,您通常会跟随以下的步骤:
- 收集您为DACL建立ACEs之信任成员的SIDs。
- 经由SIDs使用的大小及您使用的ACE结构大小来计算新DACL的大小。
- 为DACL分配内存。
- 初始化DACL。
- 使用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的内容。在这个实例中,我允许每个人拥有所有存取权。


浙公网安备 33010602011771号