AD AdminSDHolder 委派故障排查
AD AdminSDHolder 委派故障排查
同一 OU 的普通账号都能由帮助台重置密码,只有某个账号过一段时间开始报 Access is denied。若它刚被加入过 Domain Admins,除了检查操作者权限,还要比较目标对象自身的 DACL:原来的委派条目可能已经被保护机制替换。
下面用隔离域 corp.test.checo.cc 中的两个测试用户比较保护前后的权限。t5sdadmin 加入 Domain Admins,t5sdctrl 保持普通身份;两者位于同一 OU,接受相同委派。对照重点是 DACL、继承和实际密码重置结果,ACE 数量不作为固定判据。
1. 权限变化时间线
入组后二十秒,目标的 memberOf 已包含 Domain Admins,adminCount 仍为空:
SamAccountName : t5sdadmin
adminCount :
memberOf : CN=Domain Admins,CN=Users,DC=corp,DC=test,DC=checo,DC=cc后续约十六分钟的观察间隔内,adminCount 变为 1,原帮助台 Reset Password ACE 不再出现在目标 DACL 中。这个十六分钟是观测间隔,不是推导出的默认调度周期。
微软说明,保护检查默认在持有 PDC Emulator 的域控上约每 60 分钟执行,用 AdminSDHolder 的权限模板维护受保护账号和组,并关闭这些对象的权限继承。测试时刚入组就测一次,可能恰好还没发生覆盖。AdminSDHolder 与保护对象
2. DACL 与操作结果
记录中的模板与目标 DACL 字符串对照为:
ACEs=23/23 len=1457/1457 equal=True同一个帮助台身份随后重置两个测试账号:
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,...普通对照仍可操作,受保护对象失去原授权,且其 DACL 与模板一致,这组证据支持模板覆盖的解释。Access is denied 单独一行没有这种区分力。
23 和 1457 只属于当时的域模板。Exchange、委派或其他受支持配置可能改变模板;不能把固定 ACE 数量或某一组身份名单当成“已受保护”的通用指纹。
测试还观察到安全描述符 Owner 和 Group 变化,但仍应直接比较 DACL,不能只凭所有者判断委派权限。安全描述符的 O:/G: 与 DACL 是不同部分,其中 G: 也不是用户属性 primaryGroupID。
3. 检查 adminCount
下面是整理后的只读诊断示例。它从域信息选择 PDC,显式读取目标及 AdminSDHolder,并通过 Access 部分提取 DACL,避免简单按字符串 D: 切割后意外混入其他部分:
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 /A先确认该用户确实位于预期域。普通管理员若没有读取相应安全描述符的权限,读取失败也应作为结果保存,不要当成空 DACL。字符串完全相等是很强的直接比较;不相等时则继续比较具体 ACE、继承/传播标志及对象类型,不能仅凭字符串不同断言有效权限不同。
保护范围还涉及直接和传递成员关系。仅看 memberOf 可能漏掉嵌套组;adminCount 则会滞后,而且历史保护标记可能一直留着。二者都不是独立的“当前保护状态”布尔值。要结合完整组路径、继承标志、模板与目标 ACL,以及后续变化判断。
把账号从一个受保护组移出,也要确认它没有经另一条嵌套路径仍在保护范围中。将普通管理组嵌套到 Domain Admins 不能绕过这种保护,反而把其成员带进特权范围。
4. 退出特权组
这里有一条必须说准确:移出受保护组,不会自动把 adminCount 清零,也不会自动恢复继承和覆盖前的自定义 DACL。 微软明确说明 adminCount 可保留为 1,除非另外处理。微软关于历史保护状态的说明
因此,等待“下一轮自动清零”不是恢复办法。反过来,手工清 adminCount 也不会改变仍然存在的特权成员关系,更不会单独重建 OU 委派。
| 观察 | 应继续核对 |
|---|---|
| adminCount=1,仍有保护组路径 | 当前保护范围与操作流程,避免反复写会被覆盖的对象 ACE |
| adminCount=1,已无保护组路径 | 历史标记、是否仍阻断继承、原 DACL/应有委派 |
| adminCount 为空但仍在保护组 | 刚入组、检查时机与当前 ACL,不先认定不受保护 |
| 只有部分对象重置失败 | 比较其保护关系、显式 ACE、继承与真实操作身份 |
| 整个 OU 都失败 | 检查父级委派和客户端身份,同时保留对象级异常的可能 |
5. 恢复普通账号
账号确实需要保持特权时,应走能管理该保护对象的授权流程,而不是为了让普通帮助台操作成功去扩大 AdminSDHolder 模板。修改模板影响的是域内一整类受保护对象,不是当前用户一人。
对已经确认应回到普通管理模型的账号,分别处理:移除不再需要的保护成员关系;恢复所需继承/精确委派;最后按组织约定处理历史 adminCount。每一步前保存对象 GUID、DN、成员路径、原 DACL 和继承状态。
下面的恢复示例只用于已确认退出保护范围的测试账号。它开启继承并保留当前显式 ACE;模板遗留的显式条目和历史授权需要另外检查,不能依靠这一步自动恢复:
# 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 adminCount恢复后仍需审查显式条目,再用相同帮助台身份对普通目标进行授权动作和越权负对照。不要用整片默认 schema 权限替换、整域批量清标记,或无选择地恢复一份旧 ACL 来处理所有对象。
若原设计是独立显式授权,而不是继承父 OU,也应按记录恢复特定 ACE;“勾选继承”不是每个业务对象的正确答案。ACL 写入后由同一 DC 回读,多 DC 环境还要等复制到实际处理请求的节点。
6. 验收与回退
至少保留两轮证据:原问题发生时的成员关系、DACL、帮助台结果;恢复普通模型之后的新身份验证、目标 ACL 和后续观察。确认不再出现异常覆盖,而不是重置成功一次就结束。
需要回退继承调整时,先比较当前 DACL 与本次操作后的记录,再选择是否恢复保存的 DACL 与保护标志。共享管理对象存在并行变更时,应只撤销本次新增内容。清 adminCount 的回退同样按原有值或原本不存在的状态处理。
业务账号恢复后,要用原操作者重新验证目标操作,并检查原本不应获得的权限仍然被拒绝。测试账号的结果不能代替这个账号的授权检查。
