Core Concepts: Security Descriptor, DACL, and ACE
#### Security Descriptor
Every object in Active Directory (user, group, computer, OU, domain object, etc.) is a "securable object" that has a security descriptor. The security descriptor contains:
- Owner SID: The owner of the object; holds implicit and unlimited control over the security descriptor — every rule, including Deny ACEs, yields to this implicit right.
- DACL (Discretionary Access Control List): Defines which identity can do what. It is a list of ACEs.
- SACL (System Access Control List): Used for audit logging; critically important in the detection section.
> Empty DACL ≠ Null DACL. An empty DACL denies all access; a Null DACL allows all access. Null DACLs are prohibited in Active Directory.
#### ACE Structure and Evaluation Order
An access control entry (ACE) contains:
- Access Mask: A 32-bit value; defines which rights are granted/denied.
- Trustee SID: The user/group/computer to which the right applies.
- Object Type GUID: Used when the right refers to a specific attribute or extended right.
The kernel Security Reference Monitor (SRM) evaluates ACEs in the following order:
- Explicit Deny ACE
- Explicit Allow ACE
- Inherited Deny ACE
- Inherited Allow ACE
The object owner is exempt from this ordering: the owner has implicit full control over the object even if Deny ACEs are present.
#### Dangerous Rights: Attack Surface Map
The following rights allow an attacker to either perform a directly destructive operation or grant themselves broader rights:
| Right | Access Mask Name | Direct Abuse |
|---|---|---|
| GenericAll | RIGHT_GENERIC_ALL | Full control; covers all of the below |
| GenericWrite | RIGHT_GENERIC_WRITE | Write all properties of the object |
| WriteDACL | RIGHT_WRITE_DACL | Modify DACL → add rights as desired |
| WriteOwner | RIGHT_WRITE_OWNER | Transfer ownership to the attacker |
| WriteProperty | RIGHT_DS_WRITE_PROPERTY | Write the specified attribute (GUID) |
| AllExtendedRights | RIGHT_DS_CONTROL_ACCESS | Access to all extended rights |
| ForceChangePassword | User-Force-Change-Password extended right | Reset password without knowing the current one |
| Self (AddSelf) | RIGHT_DS_WRITE_PROPERTY_EXTENDED | Add yourself to a group (validated write) |
| AddMember | WriteProperty on member attribute | Add members to a group |
Abuse Chains: From Right to Action
#### ForceChangePassword → Password Reset
Anyone with the ForceChangePassword right (User-Force-Change-Password extended right) on a user can reset that account's password without knowing the current one.
Chain: Password reset → authenticate as account → privilege escalation.
Constraint: A password reset does not immediately invalidate the account's active tickets (~10-hour TGT/TGS). Additionally, the account owner may notice the password was changed. For stealthy persistence, Shadow Credentials are preferred over ForceChangePassword.
BloodHound edge: ForceChangePassword
#### WriteProperty on member / Self → Group Addition
Anyone with WriteProperty or Self (AddSelf) rights on the member attribute of a group object can add themselves or others to that group. If a protected group such as Domain Admins is targeted, an immediate privilege escalation occurs.
BloodHound edges: AddMember, AddSelf
#### WriteSPN → Targeted Kerberoasting
Anyone with write access to the servicePrincipalName attribute on a user object (via GenericWrite or WriteProperty targeting) can write a fake SPN to that user. Any authenticated user can then request a TGS for that account → the ticket is cracked offline → the account password is compromised.
> For details on Kerberoasting, see the Kerberos Ticket Attacks article. This section focuses only on the abuse mechanism of the ACL right (GenericWrite/WriteProperty).
Important: Writing servicePrincipalName does not generate Event 5136 on the DC by default; however, if a SACL is configured, 5136 is triggered. This is a blind spot that complicates detection.
BloodHound edge: WriteSPN
#### Shadow Credentials — msDS-KeyCredentialLink Write
Technical background. Starting with Windows Server 2016, PKINIT authentication can work via the Key Trust model: the public key stored in the account's msDS-KeyCredentialLink attribute is used in the KDC's AS-REQ validation. Traditional ADCS certificate infrastructure is not required.
Abuse mechanism. An attacker with write access to a account's msDS-KeyCredentialLink attribute does the following:
- Generates an asymmetric key pair.
- Writes the public key to the target object's
msDS-KeyCredentialLinkattribute (adds a shadow credential). - Authenticates as the target account via PKINIT using their private key.
- The KDC confirms validation with the stored public key and issues a TGT.
- The NT hash can also be obtained through the PKINIT U2U protocol chain → full identity takeover without needing the account password.
Why it is dangerous: The password is not changed; access persists even if the account password is reset as long as the attacker does not remove the shadow credential (because the shadow credential is a separate authentication channel).
Tools: Whisker (C#, Elad Shamir — github.com/eladshamir/Whisker), pyWhisker (Python, ShutdownRepo — github.com/ShutdownRepo/pywhisker), PKINITtools (dirkjanm).
MITRE: T1556 (Modify Authentication Process) and T1098 (Account Manipulation).
BloodHound edge: AddKeyCredentialLink
Prerequisite: Requires Windows Server 2016+ DC. The msDS-KeyCredentialLink attribute can be written by an attacker who has GenericAll, GenericWrite, or AllExtendedRights on the target object.
> Detection note: For a msDS-KeyCredentialLink change on user objects to generate Event 5136, the Audit Directory Service Changes policy must be active and a SACL with "Write" auditing must be configured on the relevant object. According to BloodHound documentation, this audit may be missed on User objects if no standard SACL configuration exists; it is captured more reliably on Computer objects.
#### WriteDACL → DACL Modification → Chaining
Anyone with WriteDACL on an object can modify that object's DACL as desired: add GenericAll to themselves, remove existing Deny ACEs, add replication ACEs for DCSync.
Chain: WriteDACL → write GenericAll to DACL → enable all attacks below.
WriteDACL on domain object: This special case is critically important. An attacker with WriteDACL on the domain object (domainDNS class) can grant DCSync rights by adding two special ACEs:
DS-Replication-Get-Changes(1131f6aa-9c07-11d1-f79f-00c04fc2dcd2)DS-Replication-Get-Changes-All(1131f6ad-9c07-11d1-f79f-00c04fc2dcd2)
These GUIDs are canonical replication rights verified via MS-ADTS and Microsoft Learn.
BloodHound edge: WriteDACL
Detection: Event 4670 (object permissions changed) + Event 4662 (operation on object).
#### WriteOwner → Take Ownership → Escalate to GenericAll
The WriteOwner right allows an attacker to transfer ownership of an object to themselves. The owner of an object bypasses all DACL restrictions, including Deny ACEs, and gains implicit GenericAll.
Escalation chain:
WriteOwner → Take ownership → GenericAll (implicit) → WriteDACL / reset password / write SPN
BloodHound edge: WriteOwner
#### Directly Granting DCSync Rights (T1003.006)
An attacker with GenericAll or WriteDACL on the domain object directly adds replication ACEs and remotely pulls NT hashes of all accounts in the domain using Mimikatz lsadump::dcsync or Impacket secretsdump.py. This operation does not require remote desktop or physical access to the DC; only an LDAP/DRSR connection is sufficient.
AdminSDHolder / SDProp: Persistent Backdoor
#### Mechanism
AdminSDHolder (DN: CN=AdminSDHolder,CN=System,DC=domain,DC=com) is Active Directory's DACL template for protected objects. The SDProp (Security Descriptor Propagator) task, which runs approximately every 60 minutes, applies this template to the following objects:
- Domain Admins, Enterprise Admins, Schema Admins
- Administrators, Account Operators, Backup Operators, Print Operators, Server Operators
- Replicator, Domain Controllers, Read-Only Domain Controllers
- And members of these groups
Protection mechanism: SDProp overwrites the DACLs of these objects with the AdminSDHolder template every cycle. This is a persistent security mechanism by design. In the attacker's hands, it becomes a persistent backdoor.
#### Attack: Adding an ACE to AdminSDHolder
If the attacker has added GenericAll or another dangerous ACE to the AdminSDHolder object:
- SDProp propagates this ACE to all protected objects in the next cycle (at most ~60 minutes later).
- Even if the defense team removes the ACE from the DA account, SDProp rewrites it in the next cycle.
- Persistence continues until the root is cleaned up (AdminSDHolder DACL is restored).
Why it is dangerous: Classic remediation actions (removing ACEs from protected objects) are insufficient. The real root cause is the AdminSDHolder DACL.
Detection: Event 5136 ObjectDN = "CN=AdminSDHolder,CN=System,DC=..." + OperationType = "Value Added".
#### SDProp Cycle Note
SDProp runs every 60 minutes by default; this value can be changed via a registry key (HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\AdminSDProtectFrequency) (value is in seconds; valid range: 60–7200 seconds). The attacker cannot directly trigger the SDProp cycle; they must wait at most ~60 minutes.
GPO ACL Abuse
Group Policy Objects (GPOs) are objects of the groupPolicyContainer class and have DACLs. An attacker with WriteProperty or GenericAll on a GPO can:
- Modify GPO contents (immediate impact: script, startup task, registry policy).
- Link the GPO to different OUs by modifying the
gPLinkattribute.
These permissions fall under MITRE T1484.001 (Group Policy Modification). GPO creation, linking, and injection of untrusted content into templates will be addressed in a separate GPO Abuse article.
Security Descriptor Hiding Techniques
Advanced threat actors hide the security descriptor to evade detection:
- Transfer ownership to the compromised user → implicit GenericAll is gained.
- Deny RIGHT_READ_CONTROL to Everyone → even administrators cannot see the ACE list; the ACL tab produces an "Access denied" message.
- Deny OU LIST_CONTENTS → the hidden object falls into an empty folder view.
These techniques make it mandatory for defense teams to perform periodic ACL auditing with tools (BloodHound, PowerView); routine scanning through the GUI is insufficient.
Attack → Detection → Prevention Summary Matrix
| Right / Technique | BloodHound Edge | MITRE | Primary Detection | Primary Prevention |
|---|---|---|---|---|
| ForceChangePassword | ForceChangePassword | T1098 | 4738 (password reset) | Least privilege; 4738 alert |
WriteProperty on member / Self | AddMember, AddSelf | T1098 | 4728/4732 (group membership) | Least privilege; group change monitoring |
| WriteSPN → Targeted Kerberoasting | WriteSPN | T1098 | 5136 servicePrincipalName (SACL required) | gMSA; SPN hygiene; 5136 monitoring |
| msDS-KeyCredentialLink → Shadow Credentials | AddKeyCredentialLink | T1098, T1556 | 5136 msDS-KeyCredentialLink | msDS-KeyCredentialLink monitoring |
| WriteDACL → DACL modification | WriteDACL | T1098 | 4670 + 4662 | Least privilege; WriteDACL auditing |
| WriteOwner → transfer ownership | WriteOwner | T1098 | 4670 + 5136 nTSecurityDescriptor | Least privilege |
| GenericAll (comprehensive) | GenericAll | T1098 | 5136 nTSecurityDescriptor | BloodHound auditing; Tiering |
| AdminSDHolder ACE | — (AdminSDHolder path) | T1098 | 5136 ObjectDN=CN=AdminSDHolder | AdminSDHolder DACL monitoring |
| Granting DCSync rights | DCSync (as result) | T1003.006, T1098 | 4662 (replication GUIDs) | Restrict replication rights |
| GPO ACL write | WriteDACL (GPO) | T1484.001 | 5136 ObjectClass=groupPolicyContainer | GPO ownership auditing |
Lab Appendix (authorized test environment only)
> This section is for explicitly authorized in writing lab/penetration testing environments only. Values are examples.
# Dangerous ACE — writing ACE with PowerView (GenericAll / WriteDACL)
Add-DomainObjectAcl -TargetIdentity "target-user" -PrincipalIdentity "attacker" -Rights All
# ForceChangePassword — password reset
# Note: requires -AccountPassword SecureString
$newPass = ConvertTo-SecureString "NewPassword123!" -AsPlainText -Force
Set-DomainUserPassword -Identity target-user -AccountPassword $newPass -Verbose
# Targeted Kerberoasting — write SPN + request ticket
Set-DomainObject -Identity target-user -Set @{serviceprincipalname='fake/spn'}
# Ticket request (Get-DomainSPNTicket has no -Identity parameter; use pipeline or -SPN)
Get-DomainUser target-user | Get-DomainSPNTicket | Format-List
# or: Get-DomainSPNTicket -SPN "fake/spn" | Format-List
# Shadow Credentials — adding msDS-KeyCredentialLink with Whisker (Windows/C#)
Whisker.exe add /target:target-user
# Shadow Credentials — pyWhisker (Linux/Python)
python3 pywhisker.py -d "domain.local" -u "attacker" -p "pass" --target "target-user" --action "add"
# Adding AdminSDHolder ACE
Add-DomainObjectAcl -TargetIdentity "CN=AdminSDHolder,CN=System,DC=domain,DC=com" `
-PrincipalIdentity attacker -Rights All