Diving into DACLs and ACEs with DACLSearch

In Active Directory, permissions are enforced through "Access Control Entries" (ACEs) stored in "Discretionary Access Control Lists" (DACLs). When misconfigured, ACEs can grant principals (users or groups) excessive rights, enabling lateral movement and privilege escalation.

Motivation

The motivation to develop a new tool came from CTFs and HackTheBox machines, where I noticed that some ACEs were not collected by "BloodHound", because they aren’t considered dangerous. For example, rights to reanimate deleted objects or specific write attributes that enable exploitation of ESC14.

A second motivation was research. When I was investigating on Abusing sAMAccountName Hijacking in "GPP: Local Users and Groups", I needed to identify user objects that could be created in containers or OUs. I couldn’t do that from BloodHound data collected during past engagements.

Don’t get me wrong, BloodHound is an incredible tool. But for the sake of exhaustivity, I wanted a tool that could give me all ACEs that a principal has on any AD object. This article will explain how DACLs and ACEs work at a level that makes DACLSearch output meaningful.

Security descriptors

In Windows, every securable object (files, registry keys, AD objects, etc.) has a security descriptor, a binary structure that describes the rights a principal has on an object. For AD objects, the security descriptor is written in the nTSecurityDescriptor LDAP attribute.

Here are the key elements of a security descriptor:

ACE structure

In this article we will focus on ACEs contained in DACLs, as SACLs are only used to generate records in the security event log when access attempts are made by a specified trustee on DACLs. There are two categories of ACEs in DACLs.

Generic ACEs, which apply simple rights directly to an object. A generic ACE contains:

Object-specific ACEs, used in Active Directory to provide more granular control. If the ACE is object-specific, the following elements are added:

Decrypting ACEs

This section describes the main components of ACEs and the values they can hold, which will help you understand the output of DACLSearch.

Security Identifier (SID)

This component holds the "Security Identifier" of the user or group object that will be granted the rights specified in the ACE. It can be any SID, even from deleted or foreign domain principals. The SID name can be resolved if it is associated with a sAMAccountName.

Access Type (AceType)

A mask that defines whether the entry allows or denies rights.

Readable Name Microsoft Enum Hex Value Description
Allowed ACCESS_ALLOWED_ACE_TYPE 0x00 Grants access rights to a trustee on the object itself.
Denied ACCESS_DENIED_ACE_TYPE 0x01 Denies access rights to a trustee on the object itself.
Allowed Object ACCESS_ALLOWED_OBJECT_ACE_TYPE 0x05 Grants access rights to a trustee on an object, property, or property set.
Denied Object ACCESS_DENIED_OBJECT_ACE_TYPE 0x06 Denies access rights to a trustee on an object, property, or property set.

Flags (AceFlags)

A mask that defines inheritance behavior for the ACE.

Readable Name Microsoft Enum Hex Value Description
Object Inherit OBJECT_INHERIT_ACE 0x01 ACE is inherited by child objects.
Container Inherit CONTAINER_INHERIT_ACE 0x02 ACE is inherited by child containers.
No Propagate Inherit NO_PROPAGATE_INHERIT_ACE 0x04 ACE will not be propagated to child objects.
Inherit Only INHERIT_ONLY_ACE 0x08 ACE is not applied on the object itself.
Inherited INHERITED_ACE 0x10 ACE was inherited, not explicitly set.

The Container Inherit flag can apply even to objects that are not defined as container object. According to Microsoft’s Containers and Leaves documentation, any object class listed in the possSuperiors or systemPossSuperiors attributes of another schema class is effectively a container, since it can hold child objects. For example, user objects are considered containers because they can have child objects.

Access Rights (AccessMask)

A mask that defines which actions the trustee can perform.

Readable Name Microsoft Enum Hex Value Description
Create Child CREATE_CHILD 0x00000001 Create new objects under this container.
Delete Child DELETE_CHILD 0x00000002 Delete child objects.
List Children ACTRL_DS_LIST 0x00000004 Enumerate objects under this container.
Self SELF 0x00000008 Validated writes.
Read Prop READ_PROP 0x00000010 Read properties or property sets.
Write Prop WRITE_PROP 0x00000020 Write properties or property sets.
Delete Tree DELETE_TREE 0x00000040 Delete all child objects.
List Object LIST_OBJECT 0x00000080 List the object itself.
Extended Right CONTROL_ACCESS 0x00000100 Perform extended operations.
Delete DELETE 0x00010000 Delete this object.
Read Control READ_CONTROL 0x00020000 Read the security descriptor.
Write DACL WRITE_DACL 0x00040000 Modify permissions.
Write Owner WRITE_OWNER 0x00080000 Take ownership of the object.
Access System Security ACCESS_SYSTEM_SECURITY 0x01000000 View/edit the SACL.
Maximum Allowed MAXIMUM_ALLOWED 0x02000000 Grants all permissions permitted by the ACL.
Generic Read GENERIC_READ 0x00020094 Composite: Read Control + List Children + List Object + Read Props.
Generic Write GENERIC_WRITE 0x00020028 Composite: Write Props + Self + Read Control.
Generic Execute GENERIC_EXECUTE 0x00020004 Composite: Read Control + List Children.
Generic All GENERIC_ALL 0x000F01FF Composite: Full control over the object.

Object Type and Inherited Object Type

Object-specific ACEs allow permissions to be applied to specific child objects, property sets, validated writes, or extended rights. These ACEs use GUIDs to precisely define the scope of access in two separate components:

ACE Inheritance and precedence

In Active Directory, permissions applied to a parent object (like an OU or container) can be inherited by child objects. This makes delegation easier but also means multiple ACEs may apply to the same object. When this happens, the system must decide which ACEs take precedence.

The rules are:

  1. Within the same DACL, "Denied" ACEs take precedence over "Allowed" ACEs.
  2. Child ACEs have precedence over inherited parent ACEs. For example, explicit ACEs (set directly on the object) always override inherited ACEs.

Note that user and group ACEs have equal weight. If a user has a direct "Allowed Full Control" ACE but is also a member of a group that has a "Denied Full Control" ACE on the same object, the deny takes precedence and the user will have no rights on the object.

How DACLSearch Works

With the model above in mind, here is how DACLSearch operates:

  1. DACLSearch dumps LDAP objects with a limited set of attributes to reduce data volume. It’s possible to dump the LDAP data to JSON with the --json argument and export all LDAP attributes with the --full argument for future use.

  2. The tool iterates over all AD objects and retrieves each object’s nTSecurityDescriptor, storing parsed ACEs, object metadata, and group membership into a SQLite database.

  3. The CLI lets you query the SQLite database to recover any ACE a principal has on any object. You can filter by principal, target object, ACE type, access mask, object type, and more.

Filtering

ACL data contains many benign, default ACEs that can crowd results. DACLSearch supports filters to reduce noise.

There are two filter types:

Some built-in filters are included based on common ACE exploitation. In the example below, we use all built-in search filters and also merge filters that exclude self-ACEs and ACEs from built-in groups such as "Administrators", "Domain Admins", "Authenticated Users", etc.

Editing and creating filters

Filters are fully editable in the CLI. You can create custom filters that include or exclude results based on:

You can also toggle the following special options:

Saving filters

Created filters can be saved as YAML files for future reuse. For example, here is a filter created to help abuse ESC14:

title: Weak explicit mapping (ESC14)
exclude_ace_flags:
  - Inherit Only
include_target_object_classes:
  - computer
  - user
include_access_masks:
  - Write Prop
include_object_types:
  - E-mail-Addresses
  - Common-Name
  - RDN
  - DNS-Host-Name
  - DNS-Host-Name-Attributes
ownership_search: false

Closing Thoughts

I hope this article was a good introduction to DACLs and ACEs and how DACLSearch works. If you have questions, don’t hesitate to contact me on X: @Toffyrak.

References