AD 审计配置与日志查询
AD 审计配置与日志查询
安全日志里有一条 4728,并不等于已经知道谁把用户加入了特权组;打开“目录服务访问”也不等于所有属性读取都会产生 4662。先确定审计配置能否留下需要的证据,再按对象身份、操作和登录会话关联,才不会把一条事件读成它没有记录的事实。
下面以 Windows Server 2022 域控 LAB-DC1 为例,配置登录、账户管理和目录访问审计,再用专用对象验证日志。域名为 corp.test.checo.cc;读访问、组成员变更和账号生命周期分别使用独立测试对象。
1. 选择审计事件
| 需求 | 事件/子类别 | 额外条件或限制 |
|---|---|---|
| 用户创建、启用、禁用、删除 | 4720、4722、4725、4726;User Account Management | 域账号在执行操作的 DC 收集;本地账号还需对应主机 |
| 全局安全组加/减成员 | 4728、4729;Security Group Management | 不是所有组作用域都用 4728 |
| 域本地安全组加/减成员 | 4732、4733;同上 | 保留完整组 SID |
| 通用安全组加/减成员 | 4756、4757;同上 | 成员可能还是一个组 |
| AD 对象访问 | 4662;Directory Service Access | 还要命中对象 SACL 的主体、权限、成功/失败条件 |
| 属性值修改 | 5136;Directory Service Changes | 需要合适的审计条件;与 4662 分工不同 |
| 登录上下文 | 4624;Logon | 在关联事件所在机器上查,受采集和保留限制 |
下文没有新增一组 5136 实验;把它列在表里,是为了避免用 4662 的“执行了读/写权限检查”代替“属性旧值和新值”。5136 的修改事件含义
2. 检查审计策略
第一组记录中,Logon 原来是 Success and Failure,SCENoApplyLegacyAuditPolicy 值缺失。实验 GPO 写入旧式 AuditLogonEvents=2 后,策略刷新使 Logon 变为 Failure。专用账号做一次成功、一次失败连接,客户端成功返回 0,但只留下 4625,没有 4624。
随后把强制子类别优先的选项置为 1,实验把 Logon 设为 Success,刷新后仍为 Success。这次成功操作产生 4624。清理测试配置并恢复原先成功/失败基线后,两类事件都再次出现。
这说明当时旧式类别配置确实能覆盖本地子类别设置。它不说明所有现代 Windows 默认都关闭了子类别优先;微软文档列出的有效默认是 Enabled,具体机器要看实际策略。配置高级审计时,在管理 GPO 中明确启用 Audit: Force audit policy subcategory settings … to override audit policy category settings,旧式审核页面保持 Not Configured;不要把“无审核”误当作“未配置”。安全选项说明
取证先读现状,不先改值:
# On the target DC; GUIDs avoid localized subcategory-name differences.
auditpol.exe /get /subcategory:'{0CCE9215-69AE-11D9-BED3-505054503030}' /r
auditpol.exe /get /subcategory:'{0CCE9235-69AE-11D9-BED3-505054503030}' /r
auditpol.exe /get /subcategory:'{0CCE9237-69AE-11D9-BED3-505054503030}' /r
auditpol.exe /get /subcategory:'{0CCE923B-69AE-11D9-BED3-505054503030}' /r
reg.exe query 'HKLM\SYSTEM\CurrentControlSet\Control\Lsa' /v SCENoApplyLegacyAuditPolicy
gpresult.exe /scope computer /h "$env:TEMP\ad-audit-gpresult.html"
wevtutil.exe gl Security四个 GUID 分别是 Logon、User Account Management、Security Group Management、Directory Service Access。命令返回值、回读策略、GPO 结果与新产生的事件放在一起。测试登录使用专用账号和可控失败次数,避开锁定阈值;不要在命令行中嵌入真实密码。
手工写 SYSVOL 安全模板或补扩展 GUID,容易造成 AD/SYSVOL 版本、客户端扩展登记不一致。使用 GPMC 维护并备份 GPO,再验证目标 OU、安全筛选、复制和结果集。域根链接通常可以继承到 Domain Controllers OU;“域根 GPO 天生不影响域控”不是成立的排查规则。
日志空缺还要看是否查错 DC、时间窗/时区、日志覆写或转发丢失。查询无匹配不等于从未发生;wevtutil 的大小与保留模式也不能独自证明某条记录一定还在。
3. 4662 触发条件
目录读访问实验对象为 kb4662-target,读取者为普通域用户 kb4662-reader。原先对象只有继承的写审计,读 department 成功却不产生目标读事件。给对象增加显式 ReadProperty 成功审计后:
| 子类别 | 对象含读审计 ACE | 目标读取对应事件数 |
|---|---|---|
| Success | 无 | 0 |
| No Auditing | 有 | 0 |
| Success | 有 | 2 |
| Success | 删除实验 ACE 后 | 0 |
两条是该次读取实际产生的数量,不是一次 LDAP 查询固定有两条。新读审计没有给读取者增加 DACL 权限;SACL 管是否记账,DACL 管是否允许。
成功事件中的关键字段为:
SubjectUserName = kb4662-reader
SubjectLogonId = 0x1467a3
ObjectServer = DS
ObjectType = %{bf967aba-0de6-11d0-a285-00aa003049e2}
ObjectName = %{16120d12-74a9-4084-b546-e5521ab27d9b}
AccessMask = 0x10本例 ObjectType 对应 user 类,ObjectName 是对象 GUID 表示,不能只按原 DN 搜;其他输出形式也应保留,别把所有事件字段都强行当 GUID。0x10 是 ReadProperty,0x20 才是 WriteProperty。Properties 中还可能出现属性集或扩展权限 GUID,未必每一项都是独立属性。4662 条件与访问位

4. 配置对象 SACL
原实验用 Everyone 的全部属性读审计方便观察,业务域不宜照搬到整个目录。下面是整理后的更窄示例:只审计专用读取者对单个测试对象 department 的成功读取;不是原实验已经运行过的配置。
# Use a dedicated test object and an AD: drive connected to the intended DC.
# Reading/writing the SACL requires appropriate audit-security privileges.
$dn = 'CN=kb4662-target,OU=KB-T10,DC=corp,DC=test,DC=checo,DC=cc'
$path = 'AD:\' + $dn
$beforeAcl = Get-Acl -LiteralPath $path -Audit -ErrorAction Stop
$beforeAcl.Audit | Format-Table IdentityReference,ActiveDirectoryRights,
AuditFlags,ObjectType,IsInherited -AutoSize
# Look up the property GUID instead of hard-coding an unverified GUID.
$dc = 'LAB-DC1.corp.test.checo.cc'
$schema = (Get-ADRootDSE -Server $dc).schemaNamingContext
$attr = @(Get-ADObject -Server $dc -SearchBase $schema `
-LDAPFilter '(&(objectClass=attributeSchema)(lDAPDisplayName=department))' `
-Properties schemaIDGUID)
if ($attr.Count -ne 1) { throw 'Expected exactly one department attribute.' }
$propertyGuid = New-Object System.Guid (,$attr[0].schemaIDGUID)
$reader = Get-ADUser kb4662-reader -Server $dc
$rule = New-Object System.DirectoryServices.ActiveDirectoryAuditRule(
$reader.SID,
[System.DirectoryServices.ActiveDirectoryRights]::ReadProperty,
[System.Security.AccessControl.AuditFlags]::Success,
$propertyGuid,
[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None)
# Inspect for an equivalent pre-existing rule before adding anything.
# Record this rule's SID, right, flags, GUID, inheritance, and the prior SACL.
$beforeAcl.AddAuditRule($rule)
Set-Acl -LiteralPath $path -AclObject $beforeAcl -ErrorAction Stop
(Get-Acl -LiteralPath $path -Audit).Audit |
Format-Table IdentityReference,ActiveDirectoryRights,AuditFlags,ObjectType,IsInheritedAD: 驱动器的连接上下文要单独确认,Get-ADUser 的 -Server 不会替换驱动器的目标 DC。实际实施前保存 SACL 原值,检查有无等价 ACE;若已经存在,就不再添加,也不能在清理时把它当本次新增项删除。
用该普通用户的独立登录会话读取 department,确认数据返回,再到处理该请求的 DC 找 Subject、ObjectName 和读访问位。负对照可以读取未纳入审计的属性;但如果现有继承 SACL 也覆盖它,仍可能有事件,不能机械要求总数为零。
清理只移除这次确实新增的准确审计规则:用新读取的 ACL 调用 RemoveAuditRuleSpecific,保留现有 DACL、其他显式 SACL 及继承项。不要删除全部 IsInherited=False 的审计项;它们可能是业务原有规则。并发 ACL 修改时,整份旧安全描述符回写也可能覆盖他人变更,需要先比较差异。
5. 组成员事件
组实验把同一测试用户依次加入三个安全组,得到:
14:46:33.424 4728 kb4728-g-global SID ...-6423
14:46:35.470 4732 kb4728-g-domainlocal SID ...-6424
14:46:37.494 4756 kb4728-g-universal SID ...-6425另一次历史测试短暂加入 Domain Admins,14:45:22.830 产生 4728,14:45:26.910 移除产生 4729。两条 SubjectLogonId 都为 0xd3bd1。普通验证不需要给测试账号真实特权组成员资格,三种专用测试组已经能验证事件覆盖。

TargetSid 指向被修改的组,MemberSid 指向加入/移除的成员,Subject 指调用身份。告警规则匹配完整组 SID;只认末段 512 会混淆不同域的 Domain Admins,BUILTIN Administrators 则是 S-1-5-32-544。名称可改,域边界不能丢。
若 Member 是组,要按当时的成员链评估影响。今天展开该组只能得到当前关系,不能自动还原历史权限范围。4729 证明某次移除操作成功,当前目录回读还要核对准确对象及直接关系;现有登录会话的特权是否已停止使用,是另一个验收问题。
6. 关联登录会话
下面在事件所在 DC 把 EventData 展开为结构化行,保留机器名、毫秒时间与 RecordId。零匹配作为查询结果处理;日志无权限、通道错误则应保留错误,不能一起吞掉:
# Execute on the DC being investigated, with access to its Security log.
function Read-SecurityEventData {
param([int[]]$Id,[datetime]$Start,[datetime]$End)
Get-WinEvent -FilterHashtable @{
LogName='Security';Id=$Id;StartTime=$Start;EndTime=$End
} -ErrorAction Stop | ForEach-Object {
$xml = [xml]$_.ToXml()
$row = [ordered]@{
Computer=$_.MachineName;Time=$_.TimeCreated
Id=$_.Id;RecordId=$_.RecordId;Version=$xml.Event.System.Version
}
foreach ($d in $xml.Event.EventData.Data) {
$row[[string]$d.Name] = [string]$d.'#text'
}
[pscustomobject]$row
}
}
$start = (Get-Date).AddHours(-2)
$end = Get-Date
$rows = @(Read-SecurityEventData -Id @(4720,4722,4725,4726,4728,4729,4732,4733,4756,4757,4662) `
-Start $start -End $end)
$rows | Sort-Object Time,RecordId |
Select-Object Computer,Time,Id,RecordId,SubjectUserSid,SubjectUserName,
SubjectLogonId,TargetSid,MemberSid,ObjectName,AccessMask先固定变更成员 SID,再查 4624:
$memberSid = 'S-1-5-21-3628476133-752301219-3504167884-6422'# Select one exact change, then correlate on the same machine/logon lifetime.
$change = $rows | Where-Object { $_.Id -eq 4728 -and $_.MemberSid -eq $memberSid } |
Sort-Object Time | Select-Object -Last 1
if (-not $change) { throw 'No matching group change in this window.' }
$logons = @(Read-SecurityEventData -Id 4624 -Start $start -End $end)
$logons | Where-Object {
$_.Computer -eq $change.Computer -and
$_.TargetLogonId -eq $change.SubjectLogonId -and
$_.Time -le $change.Time
} | Sort-Object Time | Select-Object Computer,Time,RecordId,TargetUserSid,
TargetUserName,TargetLogonId,LogonType,AuthenticationPackageName,IpAddress,ProcessName这里只在两小时窗口内找已有登录。如果原会话更早开始,应该根据保留范围扩大窗口;不是看到零条就断言没有来源。LUID 在本机、相应启动周期内才有意义,不能跨机器或跨重启按相同十六进制值拼接。
历史 0xd3bd1 对应的 4624 为 LogonType=3,名称 LAB-DC1$、SID S-1-5-18,AuthenticationPackageName=Kerberos,IpAddress 为连字符。这与该实验通过 SYSTEM 自动化执行的已知背景一致,但单靠空 IP、机器名或 VirtualAccount 不能证明某个人、某个进程或必然是本机回环。
另一个网络登录 kbjoin 的来源是 10.77.77.20,它能定位该认证上下文的网络来源;如果经过管理平台、跳板或代理,仍不能直接等同最终人的电脑。LogonType=5 是服务,计划任务常见 Batch=4,不能把两者统称为 5。相同 LUID 不保证同一自然人,不同 LUID 也不保证不同人。
要继续追人,需要管理平台作业日志、脚本执行记录、4688/相应进程遥测、远程会话与变更单。缺哪段就写明缺口,不能通过一次 4624 关联补出不存在的人类身份。
7. 账号生命周期
账户实验给出这一段历史:
| 时间 | 事件 | 对象 |
|---|---|---|
| 14:43:36.262 | 4720 创建 | kb4720-user |
| 14:43:39.283 | 4722 启用 | 同一 SID |
| 14:43:42.295 | 4725 禁用 | 同一 SID |
| 14:43:45.323 | 4726 删除 | 同一 SID |
另一条 New-ADUser -Enabled $true 调用在 17 毫秒内产生 4720 和 4722。统计创建数按创建事件及目标 SID 去重,不能把启用事件再加一次;也不要求所有工具的建号流程必须落下相同的两条。
对象 userAccountControl 为 512/514,建号记录 NewUacValue 为 0x11,不能据此说日志不可靠。事件用的是 SAM 账户标志编码,定义与 AD userAccountControl 不同。事件也不等于命令执行结束后的完整属性快照:要看完整后续事件链,当前状态再读对象。4720 的 UAC 编码说明

按 TargetSid 连接删除前后的历史,避免同名重建混淆。4726 说明当时成功删除,不能单凭它断言今天仍不存在;可能还原或重新创建。Get-ADUser 找不到同样要排除错 DC、复制、权限与查询入口,不是另一种完整历史证明。
8. 审计验收
先保存 auditpol 备份、GPO 报告、注册表值是否存在、目标 SACL 与测试对象身份,再在专用 OU 验证创建/启用/禁用/删除、三个作用域的加减成员、读审计四态。每一步保留命令结果与准确事件,不要求固定条数,也不把没有日志解释成没有操作。
收尾恢复管理 GPO 的原值及仅本次新增的 SACL、测试成员关系,再次刷新并验证基线事件。关闭 GPO 链接不保证所有安全设置自动还原;旧式值也不能不经基线核对就统一清零。日志采集应覆盖相关 DC 和本地账号所在主机,并按容量与查询频率安排保留或转发。否则排查方法正确,等事后仍可能没有足够证据。
