Least-Privilege AD OU Delegation
Least-Privilege AD OU Delegation
Delegating one writable property does not necessarily delegate only the business action intended. In one ordinary-user maintenance test, helpdesk could enable/disable accounts but also set PasswordNotRequired. Property-level authorization still needed review.
The examples configure single-object password reset and OU delegation in corp.test.checo.cc, then check child-OU inheritance and operations that should remain denied. Verification uses ordinary-operator credentials, distinct from the management identity that prepares the environment.
1. Single-object password reset
Reset Password has rights GUID 00299570-246d-11d0-a768-00aa006e0529. The single-object control added only that ACE to a disposable ordinary user, without WriteDACL, GenericAll, or an OU-wide grant.
| Phase | Ordinary operator’s LDAP result | Authentication check |
|---|---|---|
| No ACE | unicodePwd replace denied, LDAP 50 | No account change established |
| Exact Reset Password ACE | Replace succeeds over sealed SASL | New target password binds and base search returns one; clearly wrong password returns 49 |
| Exact revoke | Same reset denied again, LDAP 50 | Target DACL equals its original backup |
The evidence includes both authorization and actual target authentication. A reset call without an exception is insufficient alone, and the experiment did not establish immediate rejection of old passwords across all protocols. Early signing-only and mixed old-password tests were not treated as successful proof. unicodePwd changes require suitable TLS or supported encrypted SASL; signing is not encryption.
The grant is a sensitive identity capability: here the operator set a new password and bound as the target. Smart-card requirements, authentication policy, protocol, and logon restrictions may still constrain business access; this is not an unconditional promise to access every resource.
2. Actions and ACEs
Password reset/pwdLastSet maintenance and account creation/enable-disable are different needs. Do not combine all experimental grants into one recommended ACL.
The account-maintenance lab used Windows Server 2022 Datacenter, Windows PowerShell 5.1, ordinary operator kb06-operator, and group KB06-Helpdesk-Operators. It granted CreateChild for user on the Managed OU itself and inherited WriteProperty for userAccountControl on descendant user objects. Existing, newly created, and nested users were editable; sibling OU, unrelated properties, and group-membership modifications were denied. After revoke, an administrator-created existing control user and new account creation were denied again.
| ACE purpose | Right | ObjectType | Inheritance | InheritedObjectType |
|---|---|---|---|---|
| Create users at Managed | CreateChild | user class GUID | None | Empty |
| Change account-control property | WriteProperty | userAccountControl GUID | Descendents | user class GUID |
CreateChild is checked on the parent container. Its None inheritance here does not grant creation under every nested OU. WriteProperty is checked on the target; Descendents propagates it to the selected class. A sibling OU has no inheritance path from Managed.
The schema GUID readback was:
user bf967aba-0de6-11d0-a285-00aa003049e2
userAccountControl bf967a68-0de6-11d0-a285-00aa003049e2On Managed the new ACEs were explicit; on the existing, created, and nested users the property ACE was inherited. However, user has derived classes, including computer. Review actual object classes and inheritance behavior rather than treating a displayed user class name as the only boundary. This lab kept computers elsewhere and used ordinary business users, not protected administrator objects.
Attribute granularity still permits additional actions
userAccountControl combines multiple flags. Ordinary enabled user value 512, ACCOUNTDISABLE 2, and PASSWD_NOTREQD 32 participate in that property’s value. AD’s property ACL does not independently grant only one bit.
The same ordinary operator set PasswordNotRequired:
uac-passwordnotrequired=ALLOW
UAC_before=512;UAC_after=544This does not claim every bit is freely changeable; other controls can restrict particular operations. It establishes that this grant exceeded “disable only.” If the business action must be narrower, use a controlled service that validates target OU, object class, permitted transition, caller, and audit result.
Creation also has ownership/creator-right consequences. Revoke CreateChild and inspect objects created under that authority separately. The post-revoke property negative control deliberately used an administrator-created existing user rather than assuming the creator lost every right to their own object.
3. Prepare the OU
The lab domain was corp.test.checo.cc, NetBIOS ADLAB, DC LAB-DC1. Management required ActiveDirectory tools and permission on the dedicated OU. Create the following disposable layout, with the three existing controls created by the administrator:
OU=KB06-Helpdesk
OU=Operators
kb06-operator
KB06-Helpdesk-Operators
OU=Managed
kb06-existing
OU=Nested
kb06-nested
OU=Sibling
kb06-siblingObtain ordinary-operator credentials interactively: $cred = Get-Credential '[email protected]'. The management session continues to inspect/edit ACLs; test operations explicitly use the ordinary credential.
Preserve domain identity, target OU, operator group/primary-group state, and the original DACL. Explicit LDAP server selection avoids silently editing a different discovered domain:
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
$dc = 'LAB-DC1'
$domain = Get-ADDomain -Server $dc
if ($domain.DNSRoot -ne 'corp.test.checo.cc') { throw 'Domain mismatch' }
$root = 'OU=KB06-Helpdesk,' + $domain.DistinguishedName
$managed = 'OU=Managed,' + $root
$backup = 'C:\KB06-ACL-Backup'
New-Item -Path $backup -ItemType Directory -Force | Out-Null
Get-ADOrganizationalUnit -Identity $managed -Server $dc | Select DistinguishedName
Get-ADUser kb06-operator -Properties MemberOf,PrimaryGroupID -Server $dc |
Select SamAccountName,PrimaryGroupID,MemberOf
$entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$dc/$managed")
$entry.psbase.Options.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
$entry.RefreshCache()
$sections = [System.Security.AccessControl.AccessControlSections]::Access
$before = $entry.ObjectSecurity.GetSecurityDescriptorSddlForm($sections)
$before | Set-Content "$backup\managed-dacl.txt"Before grants, Disable-ADAccount under the ordinary credential returned insufficient access rights. The ordinary account’s primaryGroupID was 513 and its additional group was the dedicated delegation group.
4. Append scoped ACEs
Read schema GUIDs rather than guessing from display names. This adds two Allow ACEs, not delete-object, WriteDACL, or all-property permissions:
$schema = (Get-ADRootDSE -Server $dc).schemaNamingContext
$userClass = Get-ADObject -SearchBase $schema -Server $dc `
-LDAPFilter '(lDAPDisplayName=user)' -Properties schemaIDGUID
$uacAttribute = Get-ADObject -SearchBase $schema -Server $dc `
-LDAPFilter '(lDAPDisplayName=userAccountControl)' -Properties schemaIDGUID
$userGuid = New-Object Guid (,$userClass.schemaIDGUID)
$uacGuid = New-Object Guid (,$uacAttribute.schemaIDGUID)
$sid = (Get-ADGroup KB06-Helpdesk-Operators -Server $dc).SID
$acl = $entry.ObjectSecurity
$create = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$sid,
[System.DirectoryServices.ActiveDirectoryRights]::CreateChild,
[System.Security.AccessControl.AccessControlType]::Allow,
$userGuid,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None)
$write = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$sid,
[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty,
[System.Security.AccessControl.AccessControlType]::Allow,
$uacGuid,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents,
$userGuid)
$acl.AddAccessRule($create)
$acl.AddAccessRule($write)
$entry.ObjectSecurity = $acl
$entry.CommitChanges()
$entry.RefreshCache()
$entry.ObjectSecurity.Access |
Where-Object { $_.IdentityReference.Value -eq 'ADLAB\KB06-Helpdesk-Operators' } |
Select ActiveDirectoryRights,ObjectType,InheritedObjectType,InheritanceType,IsInheritedUse the same credential to test both permitted and forbidden actions. The following mutates only disposable lab objects; choose their names and OU first:
function Trial($name, [scriptblock]$action) {
try { & $action; "$name=ALLOW" }
catch { "$name=DENY;Type=$($_.Exception.GetType().Name);Message=$($_.Exception.Message)" }
}
Trial managed-create {
New-ADUser -Name kb06-created -SamAccountName kb06-created -Path $managed -Server $dc -Credential $cred
}
Trial managed-disable { Disable-ADAccount kb06-existing -Server $dc -Credential $cred }
Trial managed-enable { Enable-ADAccount kb06-existing -Server $dc -Credential $cred }
Trial later-created-disable { Disable-ADAccount kb06-created -Server $dc -Credential $cred }
Trial nested-enable { Enable-ADAccount kb06-nested -Server $dc -Credential $cred }
Trial sibling-enable { Enable-ADAccount kb06-sibling -Server $dc -Credential $cred }
Trial sibling-create {
New-ADUser -Name kb06-denied -SamAccountName kb06-denied -Path "OU=Sibling,$root" -Server $dc -Credential $cred
}
Trial managed-description { Set-ADUser kb06-existing -Description 'Unexpected' -Server $dc -Credential $cred }
Trial managed-spn {
Set-ADUser kb06-existing -Add @{servicePrincipalName='HTTP/kb06-no-service.corp.test.checo.cc'} -Server $dc -Credential $cred
}
Trial group-membership { Add-ADGroupMember KB06-Helpdesk-Operators kb06-existing -Server $dc -Credential $cred }
Get-ADUser kb06-existing -Properties Description,ServicePrincipalName -Server $dc |
Select SamAccountName,Enabled,Description,ServicePrincipalNameThe recorded matrix was:
managed-create=ALLOW
managed-disable=ALLOW
managed-enable=ALLOW
later-created-disable=ALLOW
nested-enable=ALLOW
sibling-enable=DENY;Type=ADException;Message=Insufficient access rights to perform the operation
sibling-create=DENY;Type=UnauthorizedAccessException;Message=Access is denied
managed-description=DENY;Type=ADException;Message=Insufficient access rights to perform the operation
managed-spn=DENY;Type=ADException;Message=Insufficient access rights to perform the operation
group-membership=DENY;Type=ADException;Message=Insufficient access rights to perform the operationAdministrative readback showed Description still null, ServicePrincipalName empty, and the existing user finally enabled. The created user also contained the inherited property ACE.
If one target still fails, inspect DN, its ACEs, inheritance protection, protected-account membership, and actual identity. If a newly created object works while an existing control fails, compare creator rights and inheritance. If siblings unexpectedly work, inspect additional operator groups and broader ancestor grants.
5. Restore the test DACL
This exact DACL restoration was appropriate to the exclusive disposable OU. A shared business OU needs exact removal of this change rather than overwriting another administrator’s later ACL:
$restore = (Get-Content "$backup\managed-dacl.txt" -Raw).Trim()
$acl = $entry.ObjectSecurity
$acl.SetSecurityDescriptorSddlForm($restore, $sections)
$entry.ObjectSecurity = $acl
$entry.CommitChanges()
$entry.RefreshCache()
"DACL_restored=$($entry.ObjectSecurity.GetSecurityDescriptorSddlForm($sections) -eq $restore)"
Trial revoked-disable { Disable-ADAccount kb06-existing -Server $dc -Credential $cred }
Trial revoked-create {
New-ADUser -Name kb06-revoked -SamAccountName kb06-revoked -Path $managed -Server $dc -Credential $cred
}
$user = Get-ADUser kb06-existing -Server $dc
$userEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$dc/$($user.DistinguishedName)")
"RemainingDelegatedACE=$(@($userEntry.ObjectSecurity.Access |
Where-Object { $_.IdentityReference.Value -eq 'ADLAB\KB06-Helpdesk-Operators' }).Count)"The historical result was:
DACL_restored=True
revoked-disable=DENY;Type=ADException;Message=Insufficient access rights to perform the operation
revoked-create=DENY;Type=UnauthorizedAccessException;Message=Access is denied
RemainingDelegatedACE=0Finally remove disposable users/group and then empty OUs from the deepest level. In a multi-DC domain, account for ACL replication to the DC used by the ordinary request. Restoring the DACL, restoring account state, and deleting created objects are separate checks.
6. Reset with dsacls
The November lab needed reset and pwdLastSet maintenance, not account creation or all userAccountControl writes. Select only rights for that separate need. On its existing dedicated OU/group, after preserving the original DACL:
$rp='OU=RP,OU=KB-T5,DC=corp,DC=test,DC=checo,DC=cc'
$helpdesk='ADLAB\KB-T5-Helpdesk'
dsacls.exe $rp /A
dsacls.exe $rp /I:S /G ($helpdesk + ':CA;Reset Password;user')
dsacls.exe $rp /I:S /G ($helpdesk + ':RP;pwdLastSet;user')
dsacls.exe $rp /I:S /G ($helpdesk + ':WP;pwdLastSet;user')
dsacls.exe $rp /ACA is control access; RP/WP are property read/write; /I:S selects child inheritance. The user class has derived classes, and each operation checks particular rights. Separate computer, sensitive-account, and ordinary-user OUs and test the intended allow/deny behavior for each object class. Do not broaden computer permissions merely to repair user password resets.
The record’s pwdLastSet=0 write still read back the old timestamp. That fails the operation’s outcome check; it does not prove that zero has no meaning. Microsoft defines zero’s next-logon password-change semantics with account flags. Recheck through a fresh same-DC read/RefreshCache and supported operation; arbitrary FILETIME values are not freely writable ordinary data. See pwdLastSet.
The lab also observed a group addition followed by failed tests in one context and success in a new LogonUser context. That is reason to record current identity and obtain a fresh session, not proof that every use of -Credential always reuses a stale token. Preserve DC, identity, group state, and the exact request path when interpreting those failures.
7. Blocked inheritance
Disabling inheritance can convert existing inherited ACEs to explicit ones or remove them. Both set AreAccessRulesProtected=True, but propagation differs:
| Child-OU state | Helpdesk ACE on child OU | Descendant behavior |
|---|---|---|
| Normal inheritance | Inherited ACE present | Existing/new users reset successfully |
| Block and remove inherited ACEs | Absent | Relevant inherited grants disappear; resets fail |
| Restore inheritance | Inherited ACE returns | Existing/new users regain the grant |
| Block and convert | Explicit ACE retains propagation | Descendant users can still inherit/reset |
The protection flag only says this object stops accepting parent inheritance. Inspect the ACE, object types, propagation, and descendant-specific protection/explicit grants before declaring the whole subtree broken.
In the removal control, newly created t5inh4 had zero helpdesk ACEs and reset failed. Restoring inheritance gave both old/new users inherited ACEs again. In the conversion control, t5inh6 continued working. These distinguish two options, not a fixed rule that old accounts always retain permission while new ones do not.
8. Revoke precise grants
The H143 full-DACL restore depended on an exclusive temporary OU. On shared objects, record SID, right, ObjectType, InheritedObjectType, and inheritance type, then remove the precise added ACE and retest.
dsacls /R removes that trustee’s rights on the object and may revoke grants that predated this experiment. /N involves ACL replacement. Neither is the default way to remove one newly added ACE. Parent revoke also does not erase descendant explicit grants, creator-related rights, or existing logon tokens.
For each separate workflow, verify intended success, sibling/unrelated-property/group-write rejection, and post-revoke denial on an administrator-created control. Sample existing/new users and nested OUs under real request identities, not a single administrator test.
Password-test cleanup first revoked the precise grant and compared the original DACL, then disabled/deleted disposable identities and removed the encrypted share/temporary secret directory. Directory data, authorization, and identity sessions require separate cleanup checks.
