锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 开源技术 / C++API和类开源 / Windows访问控制模型的遍历访问控制列表和ERROR_ACCESS_DENIED
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft

Windows访问控制模型的遍历访问控制列表和ERROR_ACCESS_DENIED


14. Walking an access control list.14.遍历访问控制列表。

Q. You have been supplied with an access control list. Turn this ACL into an array of access control entries.问:您已经提供了访问控制列表。将此ACL转换为访问控制条目的数组。

The result should be a table with three columns: SID, (deny | allow | audit | alarm) inheritance, and ACCESS_MASK.

  1. To walk a list of access entries in NT3.x, you first have to read off the ACL headers to get the count of ACEs (do not continue if you find out there are zero entries). To retrieve a pointer to the nth Access Control entry, you'll need to call GetAce() on the ACL. This functions returns a void*. The first byte at the pointer identifies the exact type of structure (this reminds me of a primitive version of RTTI). Once you have cast the void* into the correct structure, you can now obtain the required details from this structure. The SID is located at the SidStart member (cast this member to a SID). To check for a deny or allow, check the name of your structure (is it an ACCESS_ALLOWED_ACE struct, or an ACCESS_DENIED_ACE?). The exact type of inheritance can be obtained from the AceFlags. The last item (the access mask) can be obtained from the Mask member. Repeat this process for all ACEs.
  2. 要遍历NT3.x中的访问条目列表,您首先必须读取ACL标头以获取ACE的数量(如果发现条目为零,则不要继续)。要检索指向第n个访问控制项的指针,您需要调用GetAce()ACL。此函数返回一个void*。指针的第一个字节标识结构的确切类型(这使我想起了RTTI的原始版本)。将void*转换为正确的结构后,您现在可以从该结构中获取所需的详细信息。SID位于SidStart成员处(将此成员投射到SID)。要检查是否拒绝或允许,请检查结构的名称(是ACCESS_ALLOWED_ACE结构还是ACCESS_DENIED_ACE?)。继承的确切类型可以从AceFlags。可以从Mask成员那里获取最后一个项目(访问掩码)。对所有ACE重复此过程。
  3. It will be easiest if you convert the security descriptor to SDDL form. Then you can perform text processing on the returned security descriptor and print out the required contents from the SDDL.
  4. The CDacl and CSacl classes are derived from CAcl. You can then either obtain the columns of ACLs by calling the GetAclEntries() method (returns four arrays: SID, Access mask, type, and inheritance). Alternatively, you can loop through the ACL by row, by calling GetAclEntry(). Personally, I'd rather decrypt the ACL to SDDL form and print it there.
  5. 如果将安全描述符转换为SDDL格式,这将是最简单的。然后,您可以对返回的安全描述符执行文本处理,并从SDDL中打印出所需的内容。
  6. 在CDacl和CSacl类衍生CAcl。然后,您可以通过调用GetAclEntries()方法获得ACL的列(返回四个数组:SID,访问掩码,类型和继承)。或者,您可以通过调用逐行遍历ACL GetAclEntry()。就个人而言,我宁愿将ACL解密为SDDL格式并在那里打印。

If the ACL is a system access control list, you would not get allow / deny entries. Instead you will audit / alarm entries in the SACL. To make your walker function read from SACLs as well as DACLs, extend your walker to handle the audit and alarm ACE structs (the walker function given in the sample code can handle SACLs equally as well as DACLs).如果ACL是系统访问控制列表,则不会获得允许/拒绝条目。相反,您将在SACL中审核/警报条目。为了使您的助手功能可以从SACL和DACL中读取,请扩展助手以处理审计和警报ACE struct(示例代码中给出的助手功能可以与DACL一样处理SACL)。

void ReadDacl(const ATL::CDacl &pDacl)
{
  UINT i = 0;
  for(i = 0; i < pDacl.GetAceCount(); i++)
  {
    ATL::CSid pSid;
    ACCESS_MASK pMask = 0;
    BYTE pType = 0, pFlags = 0;
    const_cast(pDacl).GetAclEntry
        (i, &pSid, &pMask, &pType, &pFlags);
    std::wcout << pSid.AccountName() << _T(": ");
    switch (pType)
    {
      case ACCESS_ALLOWED_ACE_TYPE:
        std::wcout << _T("allow");
        break;
      case ACCESS_DENIED_ACE_TYPE:
        std::wcout << _T("deny");
        break;
      case SYSTEM_AUDIT_ACE_TYPE:
        std::wcout << _T("audit");
        break;
      case SYSTEM_ALARM_ACE_TYPE:
        std::wcout << _T("alarm");
        break;
      /* ... TODO: Repeat for the other structures */
      default:
        std::wcout << _T("Unknown");
        break;
    }
    std::wcout << _T(": ");
    if(pFlags & INHERITED_ACE)
      std::wcout << _T("Inherited: ");
    std::wcout << std::hex << 
         pMask << std::dec << std::endl;
  }
  std::wcout << std::endl;
}

Figure 18: Reading and printing a discretionary access control list.

15. Do I Have Access?我可以访问吗?

Q. You need to determine if a specific security descriptor will allow you to access an object without getting the dreaded error 5 (ERROR_ACCESS_DENIED). How do you do that?问:您需要确定特定的安全描述符是否将允许您访问对象而不会出现可怕的错误5(ERROR_ACCESS_DENIED)。你是怎样做的?

A naive implementation of this would be to look up your username in the security descriptor and directly check which accesses are granted and which are denied (GetEffectiveRightsFromAcl() can help). There are two problems using this technique.

一个简单的实现是在安全描述符中查找您的用户名,并直接检查哪些访问被授予和拒绝(GetEffectiveRightsFromAcl()可以提供帮助)。使用此技术存在两个问题。

  • Your username may not actually appear in the security descriptor—rather, your group appears in it instead.
  • One single entry may not grant you the desired access. It could be two access control entries, one that grants you some of the desired access, and one that grants the rest of the access.
  • 您的用户名实际上可能没有出现在安全描述符中,而是您的组出现在其中。
  • 一项可能无法授予您所需的访问权限。它可能是两个访问控制条目,一个授予您一些所需的访问权限,另一个授予您其余的访问权限。

The only reliable way of checking this is to actually perform the action (i.e. open the file and read it). If you succeed, you are granted access. If you fail with an error 5, you are denied access. However, if you really must...唯一可靠的检查方法是实际执行操作(即打开文件并读取文件)。如果成功,则将授予您访问权限。如果失败并显示错误5,则将拒绝访问。但是,如果您真的必须...

To check if a security descriptor grants you access, you require a call to the AccessCheck() API. The AccessCheck() API is a simplified form of the AccessCheckByTypeResultListAndAuditAlarmByHandle() API. The AccessCheckByTypeResultListAndAuditAlarmByHandle() forms the heart of the entire Windows Access Control Model. All the security APIs and objects are just a way to configure the behaviour of this little API (okay, maybe not so little!). But for our purposes, AccessCheck() should suffice. AccessCheck() may at first sight seem intimidating, but if you look at it closely, it just takes in three parameters, the security descriptor, You (your thread token), and what action you desire (the wanted access mask). The rest of AccessCheck() are just Out parameters.要检查安全描述符是否授予您访问权限,您需要调用AccessCheck()API。该AccessCheck()API是一个简化形式AccessCheckByTypeResultListAndAuditAlarmByHandle()的API。这些AccessCheckByTypeResultListAndAuditAlarmByHandle()表格构成了整个Windows访问控制模型的核心。所有安全性API和对象只是配置此小型API行为的一种方式(好的,也许不是那么少!)。但就我们的目的而言,AccessCheck()应该足够了。AccessCheck()乍看之下似乎有些令人生畏,但如果仔细观察,它只需要三个参数,即安全描述符,You(您的线程令牌)和所需的操作(所需的访问掩码)。其余的AccessCheck()只是Out参数。

You'd think that Windows can make it easier for you by making these three parameters optional. Why can't AccessCheck() just get the current thread token as default, then you pass in the filename, and AccessCheck() will look up its security descriptor itself. That's just one In parameter. Oh wait, that's just CreateFile()! That leads us back to what we first said.

您可能会认为Windows通过使这三个参数为可选可以使您更轻松。为什么AccessCheck()不能仅将当前线程令牌作为默认值,然后传入文件名,然后AccessCheck()查找其安全描述符本身。那只是一个In参数。哦,等等,就是这样CreateFile()!这使我们回到了我们最初所说的话。

Actually, AccessCheck() requires you to supply a fourth parameter, the GENERIC_MAPPING structure. This structure maps the object specific ACLs (like GENERIC_READ) into object specific rights (like FILE_GENERIC_READ). The reason why AccessCheck() needs a GENERIC_MAPPING is because it makes a call to the AreAllAccessesGranted() function, and this requires you to supply a GENERIC_MAPPING structure. Larry Osterman  offers a more complete reason why the GENERIC_MAPPING is required.

实际上,AccessCheck()需要您提供第四个参数,即GENERIC_MAPPING结构。此结构将特定于对象的ACL(如GENERIC_READ)映射到特定于对象的权限(如FILE_GENERIC_READ)。AccessCheck()需要GENERIC_MAPPING的原因是因为它调用了AreAllAccessesGranted()函数,并且这需要您提供GENERIC_MAPPING结构。拉里·奥斯特曼(Larry Osterman) 提供了要求使用的更完整的原因GENERIC_MAPPING。

  1. To check a security descriptor for access, first create a GENERIC_MAPPING structure.
  2. Gather up this structure, your security descriptor, the desired access, and your thread token.
  3. Make the first call to AccessCheck(). We expect this to fail.
  4. If the call failed with an ERROR_INSUFFICIENT_BUFFER, allocate a buffer for the PRIVILEGE_SET structure.
  5. Call AccessCheck() again with the new buffer.
  6. Check the result in the AccessStatus parameter. If true, check if the GrantedAccess member is equal to the desired access.
  7. If anything goes wrong, access is denied.
  8. 要检查安全描述符的访问权限,请首先创建一个GENERIC_MAPPING结构。
  9. 收集此结构,安全描述符,所需的访问权限和线程令牌。
  10. 调用第一个函数AccessCheck()。我们希望这会失败。
  11. 如果调用失败,并带有ERROR_INSUFFICIENT_BUFFER,则为该PRIVILEGE_SET结构分配一个缓冲区。
  12. 用新的缓冲区再次调用AccessCheck()。
  13. 检查AccessStatus参数中的结果。如果为true,请检查GrantedAccess成员是否等于所需的访问权限。
  14. 如果出现任何问题,则拒绝访问。

As was discussed in part 1, it is possible to make the access check yourself. The ten steps involved were:如第1部分所述,可以自己进行访问检查。涉及的十个步骤是:

  1. Open your token (thread or process) with OpenThreadToken().
  2. Call GetTokenInformation(TokenGroups) to retrieve the list of groups (as obtained in fig. 11).
  3. From your supplied security descriptor, access the DACL. (See fig. 15.) If it is null, you should use the DACL: Everyone (Full Control).
  4. Get the nth ACE (as shown in fig. 18).
  5. Get the SID associated with this ACE (as shown in fig. 18).
  6. Lookup this SID in the list of TOKEN_GROUPS array you obtained in step 2.
  7. Go back to the ACE and look up its type and access mask (see fig. 18).
  8. Map out any generic access rights to the supplied GENERIC_MAPPING structure using MapGenericMask().
  9. Compare the present access mask with the desired access mask.
  10. 使用打开令牌(线程或进程)OpenThreadToken()。
  11. 调用GetTokenInformation(TokenGroups)以检索组列表(如图11所示)。
  12. 在提供的安全描述符中,访问DACL。(请参见图15。)如果为null,则应使用DACL:所有人(完全控制)。
  13. 获得第n个ACE(如图18所示)。
  14. 获取与此ACE关联的SID(如图18所示)。
  15. 在TOKEN_GROUPS您在步骤2中获得的数组列表中查找此SID 。
  16. 返回ACE并查看其类型和访问掩码(请参见图18)。
  17. 使用MapGenericMask()映射所有常规访问权限对接到提供GENERIC_MAPPING的结构。
  18. 将当前的访问掩码与所需的访问掩码进行比较。
  19. To compare two ACCESS_MASKs, simply NOT one of the ACCESS_MASKs, then AND the two variables together. The result should be zero if you are granted access, otherwise you should be denied. Or you can make a call to AreAllAccessesGranted() to help you. (This API has the advantage of helping you fix-up generic access rights.)要比较两个ACCESS_MASKs,只需NOT之一,然后AND两个变量一起即可。如果您被授予访问权限,则结果应为零,否则应被拒绝。或者,您可以致电AreAllAccessesGranted()寻求帮助。(此API的优点是可以帮助您修复通用访问权限。)

  20. If the desired access mask is covered by the ACE, you are granted access.
  21. If the ACE does not completely allow access, clear out the granted accesses and continue the search.
  22. If you are at the end, deny access.
  23. 如果ACE覆盖了所需的访问掩码,则将授予您访问权限。
  24. 如果ACE不能完全允许访问,请清除授予的访问并继续搜索。
  25. 如果最后,请拒绝访问。

You could perform the 11 above steps, or you can use the AccessCheck() provided function. There isn't anything special that Windows 2000 or ATL provides to make this task easier; this technique is the same for all operating systems.

您可以执行上述11个步骤,也可以使用AccessCheck()提供的功能。Windows 2000或ATL并没有提供任何使此任务更容易的特殊功能。对于所有操作系统,此技术都是相同的。

{
  ATL::CAccessToken ProcToken, ImpersonationToken;
  ProcToken.GetEffectiveToken(TOKEN_QUERY |
    TOKEN_DUPLICATE | TOKEN_IMPERSONATE);
  ProcToken.CreateImpersonationToken(&ImpersonationToken);

  {
    BOOL AccessStatus = FALSE;
    DWORD GrantedAccess = 0, PrivilegeSetLength = 0,
    DesiredAccess = FILE_GENERIC_WRITE;
    GENERIC_MAPPING GenericMapping =
      {
        READ_CONTROL | FILE_READ_DATA |
        FILE_READ_ATTRIBUTES | FILE_READ_EA,
        FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA |
        FILE_WRITE_DATA | FILE_APPEND_DATA,
        READ_CONTROL | FILE_READ_ATTRIBUTES |
        FILE_EXECUTE,
        FILE_ALL_ACCESS
      } ;

    ::AccessCheck(const_cast
    (OutSecDesc.GetPSECURITY_DESCRIPTOR()),
      ImpersonationToken.GetHandle(), DesiredAccess, &GenericMapping,
      NULL, &PrivilegeSetLength,
      &GrantedAccess, &AccessStatus);

    ATL::CAutoVectorPtr PrivilegeSet
      (new BYTE[PrivilegeSetLength]);

    ::AccessCheck(const_cast
    (OutSecDesc.GetPSECURITY_DESCRIPTOR()),
      ImpersonationToken.GetHandle(), DesiredAccess,
      &GenericMapping, reinterpret_cast
    (static_cast(PrivilegeSet)), 
      &PrivilegeSetLength, &GrantedAccess,
      &AccessStatus);
    if(AccessStatus == TRUE)
    {
      std::wcout << std::hex <<
      GrantedAccess==DesiredAccess << std::dec;
    }
  }
}

Figure 19: Verifying if a security descriptor grants you access to an object

友情链接
版权所有 Copyright(c)2004-2015 锐英源软件

公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768

地址:郑州市文化路47号院1号楼4层(47-1楼位于文化路和红专路十字路口东北角,郑州大学工学院招待所南边,工学院科技报告厅西边。)