AD OU 最小权限委派
AD OU 最小权限委派
权限委派做到了“某个属性可写”,不一定就限制到了业务需要的那个动作。一次普通用户维护实验里,帮助台能够启停账号,同时也能设置 PasswordNotRequired;这恰好说明属性级授权仍需要继续审查。
下面在隔离域 corp.test.checo.cc 中配置单对象密码重置和 OU 委派,再检查子 OU 继承及越权分支。验证请求使用普通操作者凭据,环境搭建使用管理进程;两者身份要在日志中分清。
1. 单对象密码重置
Reset Password 扩展权限的 rights GUID 为 00299570-246d-11d0-a768-00aa006e0529。单对象实验只给一次性普通目标用户增加这条 ACE,未给 WriteDACL、GenericAll 或整个 OU 权限。
| 阶段 | 普通操作者的 LDAP 结果 | 目标认证验证 |
|---|---|---|
| 无 ACE | unicodePwd replace 被拒,LDAP 50 | 不据此修改账号 |
| 加入单一 Reset Password ACE | 密封 SASL 连接上 replace 成功 | 新目标口令 bind + base search 返回 1;明显错误口令返回 49 |
| 精确撤回 ACE | 相同 reset 再次返回 50 | 原目标 DACL 与备份一致 |
这一组既有 ACL,也有真实认证结果。不能把“重置命令没有抛异常”当成全部验收,更不能假设旧口令在所有协议中立即失效。仅启用签名却未启用 sealing 的连接不能用于证明加密要求已满足;新旧密码要在新建连接中分别验证。用于 unicodePwd 的连接要满足 TLS 或受支持的加密 SASL 条件,签名不等于加密。
这项权限本身应当按敏感身份能力管理:在本次验证中,操作者可设新密码并以目标身份绑定。业务上还可能受到智能卡、认证策略、协议和登录限制影响,不能简单宣传为无条件访问任何资源。
帮助台的“重置密码”和“建号、启停账号”是不同管理需求。本文分别看它们需要的目录权限,再用授权动作和越权动作共同验证范围;不会把两套实验中的 ACE 全部合成一张推荐权限表。
在Windows Server 2022 Datacenter、Windows PowerShell 5.1环境中,使用普通操作者kb06-operator和安全组KB06-Helpdesk-Operators测试两条OU权限:在Managed本层创建user对象,以及向Managed后代user对象继承userAccountControl写权限。结果为现有用户、授权后创建的用户和嵌套OU用户均可修改账户状态;兄弟OU、未授权属性和组成员关系被拒绝。撤回OU权限后,对管理员创建的现有用户再次禁用及在OU中再次创建用户均被拒绝。
同时发现一个容易遗漏的问题:userAccountControl的写权限覆盖整个属性,操作者除了禁用账户,还能把现有用户的PasswordNotRequired设为True,数值从512变成544。因此,业务要求“只能禁用”时,直接把这个属性委派给Helpdesk过于宽泛,应改成由受控服务执行指定动作。
2. 权限与 ACE
2.1 对象类与属性 GUID
OU的DACL保存授权主体、权限类型、对象类型和继承方式。ObjectType与InheritedObjectType都有GUID,但用途不同:
| ACE | 权限 | ObjectType | 继承方式 | InheritedObjectType |
|---|---|---|---|---|
| 创建用户 | CreateChild | user类GUID | None | 空GUID |
| 修改账户状态 | WriteProperty | userAccountControl属性GUID | Descendents | user类GUID |
CreateChild检查父容器上的创建权限,ObjectType指定能够创建的对象类。这里使用None,创建权限作用在Managed自身,不向Nested等下级OU传播。
WriteProperty检查目标对象上的属性写权限。ObjectType指定userAccountControl属性,InheritedObjectType指定由user类后代接收该ACE。Descendents覆盖后代层级,所以管理员预先创建的Nested用户也继承权限;Managed的兄弟OU没有从Managed继承的路径。
实测从测试林schema查询得到:
user bf967aba-0de6-11d0-a285-00aa003049e2
userAccountControl bf967a68-0de6-11d0-a285-00aa003049e2在Managed上,两条ACE的IsInherited=False;在kb06-existing、kb06-created和kb06-nested上,写属性ACE的IsInherited=True。用户授权后才创建,依旧继承父OU的权限,无需逐用户重新授权。
user类还存在派生类,computer就是其中之一。按user类GUID筛选继承时,应结合OU内实际对象类和派生关系检查范围;本场景把计算机放在独立的计算机OU,Managed只承载普通用户。受保护管理员账户的ACL由AdminSDHolder与SDProp维护,不采用普通业务用户的OU管理模型。
2.2 属性授权范围
userAccountControl是位标志组合。普通启用账户512,ACCOUNTDISABLE位为2,PASSWD_NOTREQD位为32。AD ACL按属性判断WriteProperty,无法在这个整数内部独立授权某一个位。
实际使用相同普通操作者调用Set-ADAccountControl -PasswordNotRequired $true得到:
uac-passwordnotrequired=ALLOW
UAC_before=512;UAC_after=544这说明应先把业务动作翻译成目录所检查的权限,再审查该权限附带的能力。管理组按业务OU隔离,敏感账户放在独立OU;需要限定到“只能禁用”这一动作时,由服务账户在后端执行,接口核对目标OU、对象类型、允许变化的位和操作者身份,并记录原值与操作结果。
创建对象也有独立的管理后果。微软说明,创建者能够操作自己创建的对象;新建对象可能带有创建者相关权限或所有权。因此,撤销父OU的CreateChild以后,应另查已创建对象的所有者和显式ACE。本次撤权的属性负对照选用管理员创建的kb06-existing,而不是用创建者自己创建的账户判断所有权限都已消失。
3. OU 委派验证
3.1 准备 OU 与操作者
环境为单DC测试域corp.test.checo.cc,NetBIOS为ADLAB,DC为LAB-DC1。执行管理命令的会话具有该测试OU的管理权限,安装ActiveDirectory模块。权限测试使用显式-Credential传入普通Helpdesk身份,避免把当前管理会话的权限混入结果。另一轮实验曾在旧上下文失败、新 LogonUser 会话成功;这要求核对当前身份与组状态,不能外推成所有 -Credential 调用必然复用旧令牌。
在“Active Directory用户和计算机”中建立下面的结构,普通用户和安全组均使用对应名称。kb06-existing、kb06-nested、kb06-sibling由管理员创建,使用符合域策略的密码并启用;普通操作者加入本次专用Global/Security组。关闭保护OU防误删选项只在最终需要删除的测试OU上使用。
OU=KB06-Helpdesk
OU=Operators
kb06-operator
KB06-Helpdesk-Operators
OU=Managed
kb06-existing
OU=Nested
kb06-nested
OU=Sibling
kb06-sibling在PowerShell中交互取得[email protected]的凭据,保存到变量$cred:$cred = Get-Credential '[email protected]'。凭据提示输入的是普通操作者密码,随后管理会话继续负责读取和修改OU权限。
先读取明确指定的DC,核对域、OU、组和测试用户。备份存放在管理员可写的本地目录;LDAP路径显式指定DC,不使用隐式域发现选择写入目标。
$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"普通操作者的PrimaryGroupID为513,额外成员关系是本次专用委派组。授权前,使用该凭据执行Disable-ADAccount得到Insufficient access rights to perform the operation。
3.2 添加限定 ACE
从schema查询GUID而不是根据显示名称猜测。创建权限作用在Managed本层,写属性权限继承到user后代。代码只追加这两条Allow ACE,没有授予删除对象、修改DACL或全属性写权限。
$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,IsInherited这两条权限可以分别按业务选择。创建用户本身不包含密码重置权限;以下New-ADUser故意创建未启用、未设密码的对象,后续密码初始化由其他明确授权的流程完成。若业务已经通过账户开通服务创建用户,Helpdesk可省去CreateChild,只保留经审查的属性维护权限。
3.3 验证允许与拒绝
先在Managed内操作,再使用完全相同的普通凭据测试Sibling、其他属性和组成员关系。错误捕获输出原始异常类型和消息,便于区分目录授权失败与请求格式、密码策略等其他原因。
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,ServicePrincipalName实际输出的关键行如下:
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 operation管理员回读现有用户的Description仍为null、ServicePrincipalName为空,账户最终保持启用。新建用户上也能看到InheritedObjectType为user类GUID的继承ACE。
若同一OU中的用户仍拒绝,先检查用户DN和ACL中的IsInherited,再检查AreAccessRulesProtected、对象是否属于受保护管理身份以及实际凭据。若新建用户能修改、现有用户拒绝,优先比较创建者权限与继承状态;若兄弟OU也能修改,应查操作者额外组成员关系及祖先OU中的更宽授权。
3.4 恢复 DACL
在这个独占的测试OU上,把已保存的DACL恢复后检查字符串一致,再用相同凭据重新禁用管理员创建的现有用户、重新创建账户。多个管理员共同修改的业务OU应按授权主体、权限、ObjectType和InheritedObjectType移除精确ACE,避免旧备份覆盖其他人的新授权。
$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)"恢复后的实际输出为:
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=0测试结束后删除本次创建的普通用户和委派组,再从最深层开始删除空OU。单DC测试域在LAB-DC1回读;多DC部署在每台可写DC检查OU与用户的DACL,等权限变更复制后再执行普通身份对照。将DACL恢复、账户状态恢复和测试对象删除分开检查,避免把撤权当成目录数据已经清理。
4. dsacls 密码重置
11 月的实验需求是重置密码及管理 pwdLastSet,而不是建号或写全部 userAccountControl。按这类需求只选择对应权限;下例是已有专属 OU 和帮助台组上的整理示例,写入前应先保存该 OU 的原 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 是控制访问权,RP/WP 是读/写属性;/I:S 选择向子对象继承。user 存在派生类,各操作也会检查不同权利,因此仅看 ACE 中的 user 标签还不够。将计算机、敏感账号与普通用户放到不同 OU,并用准确对象类验证允许与拒绝的操作;不要为修复用户密码重置而顺带扩大计算机授权。
一次测试写 pwdLastSet=0 后仍读到旧时间戳,说明还需要复核写入与读取路径,不能由此推论 0 没有作用。微软定义了 0 对下次登录改密的语义,并与账户标志共同作用。应使用同一 DC 的新读取/RefreshCache 与受支持接口复核,也不要把任意 FILETIME 当作可自由设置的普通数值。pwdLastSet 定义
5. 继承阻断
子 OU 关闭继承有两种选择:保留继承条目为显式条目,或删除已继承条目。两者都使 AreAccessRulesProtected=True,但向下传播的结果不同:
| 子 OU 操作 | 子 OU 上帮助台 ACE | 子用户结果 |
|---|---|---|
| 正常继承 | 继承条目存在 | 已有及新用户可重置 |
| 关闭并删除继承条目 | 不存在 | 已有及新用户的对应继承授权消失,重置失败 |
| 恢复继承 | 重新取得继承条目 | 原用户与新用户重新获得授权 |
| 关闭并转换为显式条目 | 条目仍存在,保持传播标志 | 下级用户仍可继承并重置 |
AreAccessRulesProtected=True 只说明该对象不再接收上级继承,不能单独证明整棵子树失去委派。还要看实际 ACE、适用对象类、传播标志以及子对象是否另有保护或显式授权。
记录中删除继承条目后,新用户 t5inh4 的帮助台 ACE 为 0,真实重置被拒;恢复继承后原有和新用户又收到 IsInherited=True 的条目。关闭并转换的另一组则保留条目,下级 t5inh6 仍能重置。它们解释的是不同选项,而非“新账号永远不继承、旧账号永远保留”的固定规律。
6. 精确撤权
上面的 H143 恢复代码只适用于独占的一次性实验 OU:已保存完整原 DACL,没有其他管理员并行修改。共享业务 OU 应记录本次 SID、权利、ObjectType、InheritedObjectType 和继承类型,精确移除对应 ACE,再重新验证。
dsacls /R 会移除该主体在当前对象上的权限,可能连带撤销它原本就有的其他授权;/N 涉及替换 ACL,都不应作为默认的“撤回本次新增一条”命令。父级撤权还不保证清理子对象的显式 ACE、创建者相关权限或已有登录令牌。
每套需求分别验收:允许动作成功;兄弟 OU、未授权属性和加组失败;撤回后管理员创建的控制对象重新被拒。检查新旧用户、嵌套 OU、真实身份和新会话,不只看一次管理员测试。
一次性账号、组和 OU 最后按记录 GUID 清理;密码实验先撤权并核对原 DACL,再禁用/删除测试身份及加密共享/临时口令目录。恢复目录状态、撤销权限和结束身份会话是分别检查的事项。
