AD-ACL-001: Non-default principals with DCSync rights¶
high Permissions MITRE ATT&CK T1003.006 Reads: LDAP
A principal outside tier 0 holds both replication rights on the domain object, so it can pull every password hash, krbtgt's included, from a domain controller (DCSync).
Also known as: dcsync
Why it matters¶
Domain controllers replicate with each other over MS-DRSR. The DC does not check that the
caller is another DC; it checks two control-access rights on the domain object:
Replicating Directory Changes and Replicating Directory Changes All. Whoever holds
both can ask any DC for the secrets of any account, which is what Mimikatz lsadump::dcsync
and Impacket secretsdump.py do.
- The attacker compromises an account (or a group member) that holds the two rights.
- They request replication of
krbtgt,Administratoror every account at once. - With krbtgt's key they forge Golden Tickets; with the other hashes they pass the hash or crack passwords. Nothing runs on the DC, so endpoint protection there sees nothing.
Legitimate holders outside the default groups are usually sync products (Entra Connect, identity management, some backup tools). Each one is a tier-0 asset whether or not it is managed like one.
How ad-doctor detects it¶
ad-doctor reads the DACL of the domain head (the effective ACEs, not inherit-only ones) and collects the SIDs that hold DS-Replication-Get-Changes and the SIDs that hold DS-Replication-Get-Changes-All. An ACE counts when it grants GenericAll, or the control-access right with that right's GUID or with no object type (all extended rights).
A principal is reported when it holds both rights and is not a default tier-0
principal: Administrators, Domain Admins, Enterprise Admins, Domain Controllers,
Enterprise Domain Controllers, (Enterprise) Read-only Domain Controllers, Schema Admins,
the built-in Administrator and SYSTEM. Each object is the principal's name, for example
MSOL_0a1b2c3d4e5f or svc_sync.
Get-Changes-In-Filtered-Set is not considered, and group membership is not expanded here: a group is reported by name, and AD-PATH-001 shows who reaches it.
Attributes and probes read
nTSecurityDescriptor of the domain head (effective ACEs)DS-Replication-Get-Changes (1131f6aa-9c07-11d1-f79f-00c04fc2dcd2)DS-Replication-Get-Changes-All (1131f6ad-9c07-11d1-f79f-00c04fc2dcd2)
Severity¶
Always high: the two rights together are enough to read the NT hash and Kerberos keys of every account in the domain without touching a DC's disk or running code on it.
False positives and limits¶
- Entra Connect with password hash sync. The
MSOL_account needs both rights. The finding is still correct, the account can DCSync, but the fix is to protect it, not to remove the rights: see AD-PRIV-011 and AD-ACL-008, and accept this finding with an owner (../ci.md#accepted-risks). - Other sync and backup products that document a need for the rights. Confirm with the vendor, then treat the account and its server as tier 0.
Fix¶
-
List who holds the rights (read-only):
$domain = (Get-ADDomain).DistinguishedName (Get-Acl "AD:$domain").Access | Where-Object { $_.AccessControlType -eq 'Allow' -and $_.ActiveDirectoryRights -match 'ExtendedRight|GenericAll' } | Format-Table IdentityReference, ActiveDirectoryRights, ObjectType, IsInherited -AutoSize1131f6aa-...is Get-Changes,1131f6ad-...is Get-Changes-All, and an all-zeroObjectTypemeans every extended right. -
Find out why each principal has them. A sanctioned sync account stays; anything else (a helpdesk group, an old migration account, an attacker's backdoor) goes.
-
Remove the grants that are not needed.
dsacls /Rremoves every ACE for the trustee on the domain object, so review step 1 first: -
If the account was not sanctioned, assume the hashes are gone: reset the krbtgt password twice (with replication in between) and the passwords of privileged accounts.
-
Protect the accounts that keep the rights as tier 0: strong unique password or gMSA, no interactive logon outside tier-0 hosts, and the server they run on managed like a domain controller.
Verify¶
To accept the risk instead, add an entry to the file passed with --accept (see accepted risks):
[[accept]]
check = "AD-ACL-001"
objects = ["..."] # optional; without it the whole check is accepted
reason = "..."
expires = 2027-03-31
Related checks¶
References¶
Microsoft and vendors
Research
Tools
MITRE ATT&CK
Available since 0.1.0.