AD AdminSDHolder Delegation Troubleshooting
AD AdminSDHolder Delegation Troubleshooting
Helpdesk can reset most ordinary users in an OU, but one account begins returning Access is denied after being added to Domain Admins. Check the target’s DACL as well as the caller’s rights: the delegated ACE may have been replaced by protected-account processing.
The examples compare two users in the isolated corp.test.checo.cc domain. t5sdadmin joined Domain Admins while t5sdctrl remained ordinary, in the same OU with the same delegation. Compare DACLs, inheritance, and actual password-reset results rather than relying on a fixed ACE count.
1. Permission timeline
Twenty seconds after addition, memberOf already contained Domain Admins but adminCount was empty:
SamAccountName : t5sdadmin
adminCount :
memberOf : CN=Domain Admins,CN=Users,DC=corp,DC=test,DC=checo,DC=ccAcross a later observation interval of roughly sixteen minutes, adminCount became 1 and the helpdesk Reset Password ACE disappeared. Sixteen minutes is an observed gap, not an inferred default scheduling interval.
Microsoft describes a default approximately 60-minute protected-account check on the PDC Emulator, using AdminSDHolder’s permissions and disabling inheritance on protected objects. An immediate test after adding membership can therefore miss the later replacement. See protected-account processing.
2. DACL and operation results
The recorded template/target comparison was:
ACEs=23/23 len=1457/1457 equal=TrueThe same helpdesk identity then produced:
RESET_FAIL CN=t5sdadmin,OU=SD,OU=KB-T5,... : Access is denied. (Exception from HRESULT: 0x80070005)
RESET_OK CN=t5sdctrl,OU=SD,OU=KB-T5,...The ordinary control remained usable, the protected object lost its delegation, and its DACL matched the template. Together those observations support template replacement; Access is denied alone does not distinguish that cause.
Counts 23 and 1457 describe this domain’s template at that time. Exchange or other supported configuration can change it. Neither a fixed ACE count nor an identity list is a universal fingerprint.
The record also observed changes to the security descriptor’s Owner and Group. Preserve that as an observation, not a rule that Owner alone diagnoses protection or must always become one particular value. O:/G: are separate from the DACL; the descriptor’s G: is also not the user’s primaryGroupID attribute.
3. Check adminCount
This reviewed read-only example selects the PDC, reads the target and template, and extracts the Access section rather than splitting SDDL at D: and potentially retaining other sections:
Import-Module ActiveDirectory
$dc = (Get-ADDomain).PDCEmulator
$domain = Get-ADDomain -Server $dc
$user = Get-ADUser t5sdadmin -Server $dc -Properties adminCount,memberOf,nTSecurityDescriptor,whenChanged
$holderDN = 'CN=AdminSDHolder,CN=System,' + $domain.DistinguishedName
$holder = Get-ADObject $holderDN -Server $dc -Properties nTSecurityDescriptor
$section = [System.Security.AccessControl.AccessControlSections]::Access
[pscustomobject]@{
DC = $dc
UserDN = $user.DistinguishedName
AdminCount = $user.adminCount
InheritanceBlocked = $user.nTSecurityDescriptor.AreAccessRulesProtected
UserOwner = $user.nTSecurityDescriptor.Owner
HolderOwner = $holder.nTSecurityDescriptor.Owner
UserAceCount = $user.nTSecurityDescriptor.Access.Count
HolderAceCount = $holder.nTSecurityDescriptor.Access.Count
DaclEqual = $user.nTSecurityDescriptor.GetSecurityDescriptorSddlForm($section) -eq
$holder.nTSecurityDescriptor.GetSecurityDescriptorSddlForm($section)
}
$user.memberOf
dsacls.exe $user.DistinguishedName /AVerify the account belongs to the intended domain. Failure to read a security descriptor must not become an empty-DACL result. Exact equality is useful direct evidence. Inequality requires comparing ACEs, order, propagation/inheritance, and object types rather than automatically claiming different effective rights.
Protection can come through transitive membership. memberOf can miss nested paths; adminCount can lag and can retain historical protection. Neither independently answers whether the object is currently protected. Combine group paths, inheritance, ACL/template comparisons, and subsequent changes.
Removing one protected-group membership is insufficient if another path remains. Nesting an ordinary administration group into Domain Admins does not bypass protection; it introduces privileged membership.
4. Leaving protected groups
Removal does not automatically clear adminCount, re-enable inheritance, or recreate the former delegated DACL. Microsoft explicitly notes that adminCount remains 1 unless separately changed. See historical protected-account state.
Waiting for automatic clearing is not recovery. Conversely, clearing the attribute does not remove current privileged membership or rebuild OU delegation.
| Observation | Follow-up |
|---|---|
| adminCount 1 and a protected membership path | Review current protection and the authorized workflow |
| adminCount 1, no protected path | Review historical flag, blocked inheritance, and intended DACL |
| Empty adminCount but protected membership | Consider recent addition, timing, and current ACL |
| Only some targets fail | Compare protection, explicit ACEs, inheritance, caller identity |
| Every target fails | Inspect parent delegation and client identity, without excluding object-specific anomalies |
5. Restore an ordinary account
If the account must remain privileged, use a workflow authorized to manage that protected account. Expanding AdminSDHolder to make ordinary helpdesk resets work changes a domain-wide class of objects, not one target.
For an account confirmed to return to ordinary management, separately remove unneeded protected membership, restore intended inheritance/precise delegation, and handle the historical adminCount flag under the organization’s policy. Preserve GUID, DN, membership paths, DACL, and inheritance state first.
Use this recovery example only for a test account confirmed out of protection. It enables inheritance and retains explicit ACEs. Review explicit template remnants and historical permissions separately; enabling inheritance does not reconstruct them:
# Only after reviewing all direct/transitive protected memberships.
$dc = 'LAB-DC1.corp.test.checo.cc'
$dn = (Get-ADUser t5sdadmin -Server $dc).DistinguishedName
$entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$dc/$dn")
$entry.psbase.Options.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
$entry.RefreshCache()
$section = [System.Security.AccessControl.AccessControlSections]::Access
$before = $entry.ObjectSecurity.GetSecurityDescriptorSddlForm($section)
$path = 'C:\ADLab-recovery-' + (Get-Date -Format yyyyMMdd-HHmmss)
New-Item -ItemType Directory -Path $path -ErrorAction Stop | Out-Null
$before | Set-Content (Join-Path $path 'dacl-before.sddl')
try {
$acl = $entry.ObjectSecurity
$acl.SetAccessRuleProtection($false,$true)
$entry.ObjectSecurity = $acl
$entry.CommitChanges()
$entry.RefreshCache()
$entry.ObjectSecurity.Access |
Select-Object IdentityReference,ActiveDirectoryRights,ObjectType,InheritedObjectType,IsInherited
} finally {
$entry.Dispose()
}
# Clear a stale flag only after scope review; this does not change the DACL.
Set-ADUser t5sdadmin -Server $dc -Clear adminCountReview explicit entries afterward. Verify both the authorized action and an unauthorized negative control under the same helpdesk identity. Do not reset whole-domain flags, replace every object from schema defaults, or indiscriminately write old ACLs back.
Some objects intentionally use explicit permissions rather than parent inheritance; re-enabling inheritance is not universally correct. Restore that design’s particular ACEs. Read changes back on the same DC and account for replication to the DC that handles real requests.
6. Validation and rollback
Preserve the original membership/DACL/helpdesk result and a post-recovery fresh identity test, target ACL, and later observation. One successful reset does not establish that unexpected replacement stopped.
Before reverting inheritance, compare the current DACL with the post-change record, then decide whether restoring the saved DACL and protection flag is appropriate. On shared objects, reverse only this change rather than overwriting concurrent administration. Restore the original adminCount value or absence when reversing that specific attribute change.
After recovering a business account, retry the intended operation as its original operator and confirm unrelated privileges remain denied. A test account's outcome does not replace this account's authorization checks.
