The Phantom Menace: Exposing hidden risks through ACLs in Active Directory

The abuse of misconfigured Access Control Lists is nothing new. However, it is still one of the main ways of lateral movement and privilege escalation within an active directory domain.

The Phantom Menace: Exposing hidden risks through ACLs in Active Directory

We often find and capitalize on these misconfigurations in our Red Team / Internal Pentests / Insider Threat assessments.

In this post, we will discuss, in a general overview, some concepts that will help us understand how Windows handles access relationships and privileges between objects and how to enumerate these relationships.

Securable objects

In Active Directory, a securable object refers to any entity or resource that can be protected by applying access controls and security attributes (security descriptors). These objects can be found in the Active Directory structure, including users, groups, computers, organizational units, printers, files, folders, and other resources.

Each securable object has an associated Security Descriptor that defines who can access the object and what actions they can perform. The Security Descriptor contains information about the object's owner, the permissions and accesses granted through DACL (Discretionary Access Control List), and the events audited through SACL (System Access Control List).

Securable objects are organized in a hierarchical structure in Active Directory, allowing access controls to be applied at different levels, from the domain to the individual object. This facilitates security management and the implementation of role and group-based access policies.

Security Descriptors

Per Microsoft documentation, "A security descriptor contains the security information associated with a securable object". A security descriptor comprises a SECURITY_DESCRIPTOR binary structure and its associated security information. This binary structure is stored as properties directly in the AD objects (nTSecurityDescriptor attribute). A security descriptor can include the following:

  • Object Owner (SID)
  • Discretionary Access Control List (DACL)
  • System Access Control List (SACL)
  • Set of control bits

The internal structure of a security descriptor is quite complex because several elements are nested within each other; for this reason, it uses its language that defines how each data block is stored within this structure, Security Descriptor Definition Language (SDDL).

Graphically, we can see all the information of a security descriptor through object properties --> Security --> Advanced.

Below is an example of the security descriptor of the Computer type object with the cname "EvilCorpCa", belonging to the EVILCORP domain.

EvilCorpCa_SD

The permissions tab refers to the DACL; all the object's DACL Access Control Entries (ACEs) appear in that table. These ACEs define which security principal (also called "trustee") has the rights on the object (here is the important point).

To keep our goal of providing an overview of the abuse and detection of misconfigurations in DACLs, we will not go deeper into SDDL and how to parse the value stored in the nTSecurityDescriptor property. Therefore, to simplify our purpose, let's use pywerview, a Python version of Powerview, which allows us to obtain the ACLs of an object, returning the ACEs in a human-readable format.

Following is an example of an LDAP query to obtain the value of the nTSecurityDescriptor the attribute of the domain object "EvilCorpCa$":

ldapsearch_ntsd

The LDAP query will return the value in binary and base64 format. To convert this value and retrieve the blocks we are interested in, the DACL list, for example, we should work with the SDDL language. We can use .NET classes to execute LDAP searches. The DirectorySearcher  .NET class contains a property named SecurityMasks that allows us to enumerate parts of the ntSecurityDescriptor property of an Active Directory object, even as a non-privileged user. We'll use pywerview to avoid executing commands on a potentially monitored Windows system to help us retrieve the ACEs.

evilcorpCa_pywerview

Access Control Lists (ACLs)

The Access Control List defines the permissions and rights granted or denied to users or groups. The ACL is associated with each object and contains a list of ACEs. Each ACE specifies a user or group and their corresponding permissions for that object.

Windows contains two types of ACLs: Discretionary access control lists (DACLs) and System Access Control lists (SACLs).

Access Control Entries (ACEs)

ACE is an element in an Access Control List (ACL). An ACL can have zero or more ACEs. Each ACE controls or monitors access to an object by a specified trustee.

All types of ACEs contain:

  • A security identifier (SID) from the trustee (security principal) to which ACE applies.
  • An access mask: specifies the access rights controlled by the ACE.
  • A flag that indicates the type of ACE
  • A set of bit flags that determine whether child containers or objects can inherit the ACE from the primary object.

We can find a list of access rights and permissions in the own Microsoft documentation.

Discretionary Access Control List (DACL)

The object DACL specifies the level of access a principal/trustee has over an object. The owner of the object controls the DACL by default.

As attackers, it is important to understand that once we compromise an object in the domain, we won't know what privileges we have over other objects until we enumerate the DACLs of those objects.

The object's owner has WRITE_DAC permissions on the object by default, allowing it to modify the DACL of the object it owns. An object requires  READ_CONTROL permission to enumerate DACLs on an object.

System Access Control List (SACL)

As discussed above, security descriptors can contain a system access control list (SACL). These SACL entries contain access control entries (ACEs).

While an object's DACL define which principal has permissions over the concerned object through its ACEs, in the case of SACL, the ACEs specify the types of access attempts that generate audit logs in a Domain Controller's security event log.

sacl_full

From the attacker's perspective, the SACL object enumeration can be useful before any modification on that object to evaluate the level of auditing of that object.

SACLs can be a great auditing feature for Blue teams to detect misconfiguration abuse, such as trying to read a specific attribute or modify an object.

Below is an example of the enumeration of the SACL of the domain object "EvilCorpCa" from pyweview:

sacl_printed

In this ACE, it is defined that all write attempts coming from the Everyone group on the EvilCorpCa object will be audited.

We will discuss SACLs in the next Lares post about auditing ACLs.

Enumerating ACLs

Now that we have an overview of using the Access Control Model to secure and attribute objects in the active directory, it's time to enumerate possible misconfiguration.

  • BloodHound:

Bloodhound maps relationships and dependencies between objects in Active Directory, such as users, groups, computers, Group Policy Objects (GPOs), and trust relationships visually and graphically:

With BloodHound, the Blue Team can quickly identify possible paths adversaries might utilize to compromise the entire domain.

In large environments where it may not be easy to digest all the information that BloodHound can give us, ADACLScanner allows us to configure many options and filters to audit ACLs (both DACLs and SACLs).

It is possible to filter by type of access (Allow/Deny), by type of permissions (GenericAll, WriteProperty...), and by Trustee. It also allows exporting the results and filtering by criticality, which gives us a clear view of the status of our environment:

acl_scanner_gui

Below is an example of a report analyzing an Organizational Unit (OU) in the domain, filtering by criticality:

adl_scanner_report-1
  • PowerShell:

We can enumerate the ACLs from PowerShell in a less graphical way from the Microsoft.PowerShell.Security module. We can leverage the Get-Acl function:

(Get-Acl "AD:$(Get-ADGroup DBOWNERS)").Access  | Select-Object ActiveDirectoryRights,IdentityReference,AccessControlType | Select-String lares

Dsacls is the equivalent of the Security tab in the properties dialog box for an Active Directory object in tools such as Active Directory Users and Computers (ADUC). You can use either tool to view and change permissions to an Active Directory object:

dsacls "CN=DBOWNERS,OU=DBS,DC=evilcorp,DC=local" | Select-String "lares"

With Powerview, it is possible to enumerate the ACEs of an object through the Get-DomainObjectACL function:

Get-DomainObjectAcl -Identity DBOWNERS | ? { ($_.SecurityIdentifier -match '^S-1-5-.*-[1-9]\d{3,}$')} | select SecurityIdentifier,ActiveDirectoryRights, @{name='trustee';expression= {Convert-SIDToName $_.SecurityIdentifier }} | fl
  • Pywerview:

With Python we can use pywerview to obtain the DACL and SACL by parsing the GUID and SID for more convenience:

pywerview get-objectacl -u lares -w EvilPass1. -t 192.168.1.45 --resolve-sids --resolve-guids --name WSRV01                     

The wrirte_property permission indicates we have access to modify a specific attribute on an object. We can refer to Microsoft's documentation to map the objectacetype value to a human-readable Active Directory attribute:

Abusing ACLs

Below are some examples of ACLs misconfiguration abuses. This is not an exhaustive set of attack paths - it is up to the tester to be creative and consider operational security when attempting to exploit misconfigurations.

GenericAll

All possible access rights. All possible abuse cases :).

  • Permission value: ADS_RIGHT_GENERIC_ALL.

Let's use as an example a misconfiguration in the ACLs of a Active Directory Certificate Services (ADCS) certificate template in which a user has GenericAll permissions on a template (ESC4). We can modify the template using certipy:

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 5136 [A directory service object was modified]
  • 4900 [ Certificate services template security was updated]

GenericWrite

Permission to write all properties of the object. The ObjectType member of an ACE can contain a GUID that identifies a property set or property. If ObjectType does not contain a GUID, the ACE controls the right to write all the object's properties.

  • Permission value: GENERIC_WRITE

In the following example, we will use pywhisker to write the msDS-KeyCredentialLink the attribute of a target user/computer to gain full control over that object (ShadowCredentials):

pywhisker -d "EvilCorp.local" --dc-ip 192.168.1.45 -u "lares" -p "EvilPass1." --action "add" --target "EVILDC01$" --filename evildc01

Then request  TGT to get a PAC for the current user using U2U (When the TGT was obtained using PKINIT, the PAC will contain the NT hash):

gettgtpkinit.py -cert-pfx evildc01.pfx -pfx-pass blablapass -dc-ip 192.168.1.45 EVILCORP.LOCAL/EVILDC01$ evildc01.ccache

Export the .cacche and get the NT hash:

export KRB5CCNAME=evildc01.ccache

getnthash.py -key 23123123.. EvilCorp.local/EVILDC01$ -dc-ip 192.167.1.45

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 5136 [A directory service object was modified]. (msDS-KeyCredentialLink).
  • 4768 [A Kerberos authentication ticket (TGT) was requested].
  • 4769 [A Kerberos service ticket was requested].

WriteProperty

Permission to write over specific attributes. The attribute can be found in the GUID ObjectType GUID. Account Operators usually have this right.

  • Permission value: ADS_RIGHT_DS_WRITE_PROP
  • Over computers: Shadow Credentials, RBCD, SPN Jacking.
  • Over users: Shadow Credentials, Targeted Kerberoasting, Logon Script.
  • Over Groups: AddMember.

For this example the lares user has write permissions over the serviceprincipalname (WriteSPN) the attribute of EvilDA. We can subsequently add SPN value to that account using targetedKerberoast.py, effectively making that account "Kerberoastable":

The retrieved hash is then run through hashcat to retrieve the plaintext password of the Domain Admin account.

hashcat -m 13100 --force -a 0 EVILDA.txt superPasswordList.txt

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 4768 [A Kerberos authentication ticket (TGT) was requested].
  • 4769 [A Kerberos service ticket was requested].
  • 5136 [A directory service object was modified].

WriteDacl

The right to modify the DACL in the object's security descriptor.

  • Permission value: ADS_RIGHT_WRITE_DAC.

We can change the DACL of our target using ldap_shell by specifying the value of the ACE we want, in this case, GenericAll (0x10000000).

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 4665 [An operation was performed on an object] (Object-Access, Read + Write_DAC).
  • 4742 [A computer account was changed].

WriteOwner

The right to assume ownership of the object. The user must be an object trustee. The user cannot transfer ownership to other users.

  • Permission value: ADS_RIGHT_WRITE_OWNER

Using ldap_shell again we can set ourselves, the lares user, as the owner of the target object:

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 4665 [An operation was performed on an object] (Object-Access, Read + Write_DAC).
  • 4737 [A security-enabled local group was changed].

Self (Self-Membership)

Validated write permission to update group membership regarding adding/removing one's account.

  • Rights-GUID: bf9679c0-0de6-11d0-a285-00aa003049e2

In this example, we add ourselves,  lares user to the target group using ldap_shell via NTLM auth:

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 4665 [An operation was performed on an object] (Object-Access, Read + Write_DAC).
  • 4737 [ A security-enabled global group was changed]

AllExtendedRights

Rights to perform operations controlled by an extended access right. If ObjectType does not contain a GUID, the ACE controls the right to perform all extended rights operations. This permission allows for resetting passwords on User objects and for crafting a Resource-Based Constrained Delegation (RBCD) attack for Computer objects.

  • Permission value: ADS_RIGHT_DS_CONTROL_ACCESS.
  • Over Group: AddMember.
  • Over User: ForceChangePassword.
  • Over Computers: ReadLAPSPassword.
  • If a domain object with AllExtendedRights permissions on the domain object itself is compromised, that domain object will have both the DS-Replication-Get-Changes and DS-Replication-Get-Changes-All privilege. Both rights allow a principal to replicate objects from the Domain (DCSync).

For this example, our lares user has AllExtendedRights over EvilCorp.local. We can use secretsdump to perform DCSync:

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 4662 [An operation was performed on an object].

ReadLAPSPassword (ExtendedRight)

ExtendedRight, right to read ms-mcs-admpwd attribute over a computer configured for LAPS.

  • Rights-GUID:`2096ca39-21e0-479a-86f1-d06725a9b5d9`.

Since the Local Administrator Password Solution (LAPS) password is stored in cleartext within the ms-mcs-admpwd attribute, we only need to perform an LDAP query or use pywerview to read that attribute value on the machine we have permissions on:

ldapsearch -H ldap://192.168.1.45 -D 'Lares@evilcorp.local' -w 'EvilPass1.' -b "dc=EVILCORP,dc=LOCAL" "(ms-MCS-AdmPwd=*)" ms-MCS-AdmPwd

Events:

  • 4776 [The computer attempted to validate the credentials for an account].
  • 4624 [An account was successfully logged on].
  • 4662 [An operation was performed on an object.] (ReadProperty).

ForceChangePassword (ExtendedRight)

Right to reset a password on a user account.

  • Rights-GUID: 00299570-246d-11d0-a768-00aa006e0529

We can modify the target user's password using ldap_shell with Kerberos auth:

  •  4768 [A Kerberos authentication (TGT) was requested].
  •  4769 [ A Kerberos service ticket was requested].
  •  4624 [An account was successfully logged].
  •  4724 [ An attempt was made to reset an account's password].

Summary

ACLs misconfigurations are among the most common lateral movement and privilege escalation methods in Active Directory. Our goal with this post was to summarize how permissions are related between objects in Active Directory, what ACLs are and how they work, and how they can be abused during an assessment.

From a defensive perspective, auditing ACL misconfigurations can be difficult, particularly in large environments. Many events generated by abusing these misconfigurations are not logged by default in Windows, so configuration and tuning will be required.

In our next post, we will talk specifically about how to audit your Active Directory environment to avoid misconfigurations and abuse of ACLs.

Acknowledgements