访问检查Active Directory
与许多Windows相关技术一样,Active Directory使用安全描述符和访问检查过程来确定用户对目录各部分的访问权限。目录中的每个对象都包含一个nTSecurityDescriptor属性,该属性存储安全描述符的二进制表示形式。当用户通过LDAP访问对象时,远程用户的令牌与安全描述符一起用于确定他们是否有权执行所请求的操作。
安全描述符薄弱是一种常见的配置错误,可能导致整个域被攻陷。因此,管理员能够发现并修复安全弱点非常重要。不幸的是,Microsoft没有为管理员提供审计AD安全性的方法,至少在我知道的任何默认工具中都没有。虽然有第三方工具(如Bloodhound)可以离线执行此分析,但从其检查实现的阅读来看,它们往往不使用真实的访问检查API,因此可能会遗漏一些配置错误。
我为AD编写了自己的访问检查器,该检查器包含在我的NtObjectManager PowerShell模块中。我用它发现了一些漏洞,例如CVE-2021-34470,这是Exchange对AD的更改所导致的问题。该工具是“在线”工作的,即您需要在域中有一个活动帐户才能运行它,但据我所知,如果您对特定用户对AD对象的访问权限感兴趣,它应该提供最准确的结果。虽然该命令在模块中可用,但如何使用和解释结果可能并不立即显而易见,因此我决定写一篇快速的博客文章来介绍它。
复杂的过程
访问检查过程主要由Microsoft在[MS-ADTS]:Active Directory技术规范中记录。具体在第5.1.3节中。然而,这留下了许多未解答的问题。我也不打算全面介绍其工作原理,但让我快速概述一下。我假设您对AD的结构及其对象有基本的了解。
AD对象包含许多资源,可能希望为特定用户授予或拒绝对这些资源的访问。例如,您可能希望允许用户仅创建特定类型的子对象,或仅修改某些属性。Microsoft本可以用许多方式实现安全性,但他们决定扩展ACL格式以引入对象ACE。例如,ACCESS_ALLOWED_OBJECT_ACE结构在正常的ACCESS_ALLOWED_ACE中添加了两个GUID。
第一个GUID ObjectType表示ACE适用的对象类型。例如,可以将其设置为属性的架构ID,ACE将仅授予对该属性的访问权限,而不授予其他权限。第二个GUID InheritedObjectType仅在ACL继承期间使用。它表示允许继承此ACE的对象类的架构ID。例如,如果将其设置为计算机类的架构ID,则仅当创建此类时才会继承ACE,如果创建的是用户对象则不会继承。在执行访问检查时,我们只需要关心第一个GUID。
要执行访问检查,您需要使用诸如AccessCheckByType之类的API,该API支持检查对象ACE。调用API时,您传递要检查访问的对象类型GUID列表。处理DACL时,如果ACE的ObjectType GUID不在传递的列表中,它将被忽略。否则,它将根据正常的访问检查规则进行处理。如果ACE不是对象ACE,它也会被处理。
如果您只想检查本地用户是否有权访问特定对象或属性,那么这很简单。只需获取该用户的访问令牌,将对象的GUID添加到列表中,并调用访问检查API。生成的授予访问权限可以是以下特定访问权限之一(括号中的名称是我在PowerShell模块中为简化而使用的名称):
- ACTRL_DS_CREATE_CHILD (CreateChild) - 创建新的子对象
- ACTRL_DS_DELETE_CHILD (DeleteChild) - 删除子对象
- ACTRL_DS_LIST (List) - 枚举子对象
- ACTRL_DS_SELF (Self) - 授予写入验证的扩展权限
- ACTRL_DS_READ_PROP (ReadProp) - 读取属性
- ACTRL_DS_WRITE_PROP (WriteProp) - 写入属性
- ACTRL_DS_DELETE_TREE (DeleteTree) - 删除对象树
- ACTRL_DS_LIST_OBJECT (ListObject) - 列出对象树
- ACTRL_DS_CONTROL_ACCESS (ControlAccess) - 授予控制扩展权限
您还可以被授予标准权限,如READ_CONTROL、WRITE_DAC或DELETE,这些权限的功能与您期望的一样。然而,如果您想查看在DC上授予的最大访问权限,则稍微困难一些。我们面临以下问题:
- 授予本地用户的组列表可能与在真实访问检查发生的DC上授予的组列表不匹配。
- AccessCheckByType仅返回单个授予的访问值,如果我们有很多对象类型要测试,则对单个安全描述符调用数百甚至数千次将非常昂贵。
虽然您可以通过拥有足够的本地权限来手动创建访问令牌来解决第一个问题,并通过使用返回授予访问列表的API(如AccessCheckByTypeResultList)来解决第二个问题,但有一个“更简单”的解决方案。您可以使用Authz API,这些API允许您手动构建具有任何所需组的安全上下文,而无需创建访问令牌,并且AuthzAccessCheck API支持为类型列表中的每个对象返回授予的访问列表。恰巧,该API正是AD LDAP服务器本身使用的API。
因此,要执行“正确”的最大访问检查,您需要执行以下步骤:
- 从AD枚举用户在DC上的组列表。本地组分配存储在目录的CN=Builtin容器中。
- 使用组列表构建Authz安全上下文。
- 读取目录对象的安全描述符。
- 读取对象的架构类,并构建要检查的特定架构对象列表:
- 来自类及其超类、辅助类和动态辅助类的所有属性。
- 所有允许的子对象类。
- 所有可分配的控制、写入验证和属性集扩展权限。
- 将收集的架构信息转换为访问检查的对象类型列表。
- 运行访问检查并处理结果。
- 对要检查的每个对象重复步骤3。
相信我,这个过程实际上说起来容易做起来难。有许多细微差别会产生令人惊讶的结果,我猜这就是为什么大多数工具都不愿意麻烦的原因。此外,我的代码包含从逆向工程真实实现中收集的大量知识,但我肯定我可能遗漏了一些东西。
使用Get-AccessibleDsObject并解释结果
让我们最终开始使用PowerShell命令,这是本文的真正目的。要进行简单检查,请运行以下命令。首次运行时可能需要一段时间来收集有关域和用户的信息。
|
|
这使用NamingContext属性指定要检查的对象。该属性允许您轻松指定三个主要目录:Default、Configuration和Schema。您还可以使用DistinguishedName属性指定显式DN。此外,Domain属性用于指定LDAP服务器的域(如果您不想检查当前用户的域)。您还可以指定Recurse属性以递归枚举对象,在这种情况下,我们仅访问检查根对象。
访问检查默认使用当前用户的组(基于他们在DC上的组)。这显然很重要,特别是如果当前用户是本地管理员,因为他们不能保证在DC上拥有管理员权限。您可以使用UserSid属性通过SID指定要检查的不同用户,或使用UserName属性通过名称指定。这些属性可以接受多个值,这将针对枚举的对象列表运行多个检查。例如,要使用域管理员进行检查,可以执行以下操作:
|
|
访问检查结果的基本表格格式显示五列:对象的通用名、其架构类、被检查的用户以及访问检查是否导致授予任何可修改或可控制的访问权限。可修改是指能够写入属性或创建/删除子对象等。可控制表示授予用户一个或多个可控制的扩展权限,例如允许更改用户密码。
由于这是PowerShell,访问检查结果是一个具有许多属性的对象。在确定授予用户的访问权限时,以下属性可能是最感兴趣的:
- GrantedAccess - 仅在检查期间指定对象的架构类时授予的访问权限。如果在此级别授予访问权限,它将应用于该类型的所有值,例如,如果授予WriteProp,则用户可以写入对象中的任何属性。
- WritableAttributes - 用户可以修改的属性列表。
- WritablePropertySets - 用户可以修改的可写属性集列表。请注意,这更多是为了提供信息,可修改的属性也将出现在WritableAttributes属性中,这将更容易检查。
- GrantedControl - 授予用户的控制扩展权限列表。
- GrantedWriteValidated - 授予用户的写入验证扩展权限列表。
- CreateableClasses - 可以创建的子对象类列表。
- DeletableClasses - 可以删除的子对象类列表。
- DistinguishedName - 对象的完整DN。
- SecurityDescriptor - 用于检查的安全描述符。
- TokenInfo - 用于检查的用户信息,例如组列表。
该命令应该很容易使用。也就是说,它确实带有一些注意事项。首先,您只能使用域帐户直接访问AD来使用该命令。从技术上讲,没有理由不能像Bloodhound那样实现收集器并离线进行访问检查,但我没有这样做。我还没有在更奇怪的设置(如复杂的域层次结构或RODC)中测试它。
如果您使用低权限用户,可能会有一些AD对象无法枚举或读取其安全描述符。这意味着结果将取决于您用于枚举的用户。最好的结果是使用具有完全访问权限的域/企业管理员。
根据我的测试,当我发现授予用户的访问权限似乎是真实的时,但有可能我并不总是100%正确,或者我遗漏了某些访问权限。此外,值得注意的是,仅仅拥有访问权限并不意味着LDAP服务器不会进行一些额外的检查。例如,即使在计算机对象中创建组托管服务帐户似乎是被授予的子对象,也存在明确的阻止。