
Уведомление об изменениях AD начинается с настройки источника событий. Сравнение двух списков пользователей не показывает каждое промежуточное действие, а опрос одного контроллера не видит все изменения домена. Ниже — настройка аудита, сбор событий с контроллеров через PowerShell, структурированная выгрузка и сигнал для системы мониторинга. Скрипт не требует паролей в исходнике и не рассылает журналы во внешние сервисы.
1. Определите контролируемые действия
Разделите создание, удаление и изменение объектов каталога, изменение членства в группах, управление учётными записями и изменение политик. Для каждого класса назначьте приоритет и владельца реакции. Добавление человека в обычную рабочую группу и добавление в привилегированную группу не должны создавать одинаковую тревогу.
Начните с небольшого списка критичных объектов и групп, затем расширяйте покрытие. События могут содержать имена, DN и значения атрибутов; отчёты доступны только уполномоченным сотрудникам. Не включайте бездумно аудит всех атрибутов всего домена: избыточный поток усложняет расследование и ускоряет ротацию журнала.
2. Включите нужные категории GPO на контроллерах
Создайте отдельную политику для OU Domain Controllers и сохраните её исходное состояние. В Advanced Audit Policy включите Success для Directory Service Changes и Security Group Management; для управления пользователями добавьте User Account Management. Учитывайте результирующие политики и настройку приоритета расширенных подкатегорий над базовыми категориями.
Названия в интерфейсе зависят от языка ОС. Для проверки фактически применённых подкатегорий используйте:
auditpol /get /category:*
gpresult /scope computer /r
Directory Service Changes требует также подходящей SACL на объекте. Включение подкатегории не создаёт универсальный аудит всех объектов. Категории и ограничения описаны в руководстве Microsoft по расширенному аудиту и описании Directory Service Changes.
Перед изменением выбранной политики сохраните её штатную копию и отчёт. Пример относится к уже созданной GPO AVG-DC-Audit; имя замените своим, каталог предварительно ограничьте администраторами:
Import-Module GroupPolicy
New-Item -ItemType Directory -Path C:\AdminReports\GPOBackup -Force | Out-Null
$gpo = Get-GPO -Name 'AVG-DC-Audit'
Backup-GPO -Guid $gpo.Id -Path C:\AdminReports\GPOBackup
Экспортируйте XML-отчёт выбранной политики:
$report = @{
Guid = $gpo.Id
ReportType = 'Xml'
Path = 'C:\AdminReports\GPOBackup\audit-before.xml'
}
Get-GPOReport @report
На каждом DC сохраните auditpol /backup /file:C:\AdminReports\audit-before.csv из привилегированного сеанса и затем проверьте эффективную политику. Локальный auditpol backup не заменяет backup GPO: повторное применение доменной политики может вернуть настройки. Формат штатной копии GPO описан в Backup-GPO.
3. Добавьте SACL на пилотную OU
В AD Users and Computers включите Advanced Features, откройте Security → Advanced → Auditing на отдельной тестовой OU. Добавьте согласованного субъекта аудита, Success и только нужные операции изменения для нужного типа объектов и наследования. Сохраните исходную SACL и проверьте распространение на тестовый дочерний объект.
SACL задаёт, что записывать, а DACL — кому разрешать доступ. Не заменяйте DACL ради аудита. Измените тестовый атрибут разрешённого объекта и найдите событие на контроллере, обработавшем изменение. Если события нет, проверьте категорию, SACL, наследование, время и выбранный DC. Для защищённых административных объектов наследование может отличаться; пилотная OU не подтверждает покрытие всех привилегированных учётных записей.
4. Читайте правильные события и XML-поля
Для изменения объекта полезны 5136, для создания — 5137, перемещения — 5139, удаления — 5141. Членство глобальных групп отражается 4728/4729, локальных — 4732/4733, универсальных — 4756/4757. Управление учётными записями имеет отдельные события, например 4720, 4722, 4725, 4726 и 4738.
Событие 5136 может содержать отдельные записи удаления старого и добавления нового значения. Сопоставляйте объект, атрибут, время и correlation ID, а не считаете каждую строку самостоятельной человеческой операцией. Поля описаны в документации события 5136.
Для скрипта используйте ToXml() и имена EventData/Data. Текст Message локализован и может различаться между системами. Сохраняйте имя компьютера, ID, RecordId, UTC и исходные поля; это основа повторяемого расследования.
5. Соберите ограниченное окно со всех DC
Скрипт ниже рассчитан на Windows PowerShell 5.1, RSAT ActiveDirectory и согласованный доступ к Security контроллеров. У оператора должны быть права чтения журналов и разрешён необходимый сетевой путь удалённого Event Log. Не выдавайте Domain Admin только ради чтения. Служебную учётную запись или gMSA используйте по принятой процедуре.
Создайте защищённый каталог C:\AdminReports\AD-Audit, сохраните код как Export-AdAudit.ps1. Запуск не меняет каталог: он читает последние 15 минут со всех обнаруженных DC, пишет JSONL и локальный файл оповещения. При достижении предела выгрузки скрипт сообщает ошибку вместо молчаливого пропуска.
param([int]$Minutes = 15, [string]$OutDir = 'C:\AdminReports\AD-Audit')
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
if ($Minutes -lt 1 -or $Minutes -gt 1440) { throw 'Invalid interval' }
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
$end = Get-Date
$start = $end.AddMinutes(-$Minutes)
$stamp = [DateTime]::UtcNow.ToString('yyyyMMdd-HHmmss-fff')
$ids = @(5136,5137,5139,5141,4728,4729,4732,4733,4756,4757,
4720,4722,4725,4726,4738)
$records = [Collections.Generic.List[object]]::new()
$failures = [Collections.Generic.List[object]]::new()
foreach ($dc in Get-ADDomainController -Filter *) {
try {
$events = @(Get-WinEvent -ComputerName $dc.HostName -FilterHashtable @{
LogName = 'Security'; Id = $ids; StartTime = $start; EndTime = $end
} -MaxEvents 10000 -ErrorAction Stop)
if ($events.Count -ge 10000) { throw 'Window reached collection limit' }
foreach ($event in $events) {
[xml]$xml = $event.ToXml()
$fields = @{}
foreach ($d in $xml.Event.EventData.Data) {
$fields[[string]$d.Name] = [string]$d.'#text'
}
$records.Add([pscustomobject]@{
Computer = $event.MachineName
EventId = $event.Id; RecordId = $event.RecordId
TimeUtc = $event.TimeCreated.ToUniversalTime().ToString('o')
Data = $fields
})
}
} catch {
if ($_.FullyQualifiedErrorId -notlike 'NoMatchingEventsFound*') {
$failures.Add([pscustomobject]@{
DC = $dc.HostName; Error = $_.Exception.Message
})
}
}
}
$file = Join-Path $OutDir "$stamp.events.jsonl"
$lines = @($records | ForEach-Object { $_ | ConvertTo-Json -Depth 6 -Compress })
[IO.File]::WriteAllLines($file, [string[]]$lines, [Text.UTF8Encoding]::new($false))
$critical = @($records | Where-Object EventId -In @(4728,4732,4756,5141))
$notice = [pscustomobject]@{
WindowStartUtc = $start.ToUniversalTime().ToString('o')
WindowEndUtc = $end.ToUniversalTime().ToString('o')
Collected = $records.Count; AttentionCandidates = $critical.Count
Errors = $failures.ToArray(); EvidenceFile = $file
}
$notice | ConvertTo-Json -Depth 6 |
Set-Content (Join-Path $OutDir "$stamp.notice.json") -Encoding UTF8
if ($failures.Count -gt 0) { exit 2 }
if ($critical.Count -gt 0) { exit 3 }
exit 0
Код 3 означает наличие кандидатов на проверку, а не доказанный инцидент: в примере приоритетная фильтрация ещё не ограничена SID конкретных критичных групп. Настройте её под вашу модель. Код 2 означает неполный сбор и должен создавать отдельную тревогу.

6. Подключите сценарий к мониторингу
Выполните тестовый запуск из согласованной служебной учётной записи. Проверьте JSONL, notice-файл и код завершения. Затем создайте задачу планировщика с явным путём к скрипту и без секретов в аргументах. Пример команды запуска:
powershell.exe -NoProfile -File C:\AdminTools\Export-AdAudit.ps1 -Minutes 15
Пусть существующий агент мониторинга трактует exit 2 как ошибку сбора, exit 3 как необходимость проверки, а 0 — как успешный опрос без выбранных кандидатов. Если используется планировщик, отдельно настройте контроль Last Run Result и отсутствия свежего notice-файла; сам планировщик не рассылает уведомления по этим кодам автоматически. Не включайте ExecutionPolicy Bypass вместо принятой процедуры подписи и разрешения скриптов.
Проверьте код задачи из планировщика, чтобы система мониторинга различала отсутствие изменений и потерю сбора:
Get-ScheduledTaskInfo -TaskName 'AVG-AD-Audit' |
Select-Object LastRunTime, LastTaskResult, NextRunTime
Get-ChildItem 'C:\AdminReports\AD-Audit' -Filter '*.notice.json' |
Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1
Имя относится к созданной вами задаче. Последний файл должен соответствовать последнему успешному окну, а не старому запуску. LastTaskResult 0 при запуске сторонней оболочки может скрывать код дочернего PowerShell: задайте прямой executable/action и убедитесь контролируемой ошибкой, что код 2 сохраняется. Заблокированный доступ к одному DC должен давать notice.Errors и сигнал неполного сбора, а не «всё спокойно».
7. Учтите дубли и пропуски опроса
При запуске раз в пять минут и окне пятнадцать минут записи повторяются. Для дедупликации используйте источник, RecordId и сведения о жизненном цикле журнала. После очистки журнала RecordId может использоваться заново, поэтому одного номера недостаточно. Учитывайте задержку репликации, часы DC и остановку задачи.
Этот скрипт — ограниченная выгрузка для проверки и небольшого внедрения. Он не является гарантированно непрерывным журналом: простой длиннее окна или ротация Security могут потерять события. Настройте контроль свежести, объёма, ошибок и процедуры расширенной выгрузки. Не увеличивайте окно бесконечно без оценки нагрузки и объёма.
Для просмотра и дедупликации выгруженных окон используйте только собственный набор файлов. Следующий пример объединяет события по Computer/RecordId/TimeUtc; включение времени снижает риск совпадения номера после сброса журнала, но не заменяет контроль его жизненного цикла:
$events = Get-ChildItem 'C:\AdminReports\AD-Audit' -Filter '*.events.jsonl' |
ForEach-Object { Get-Content -LiteralPath $_.FullName } |
Where-Object { $_.Trim() } |
ForEach-Object { $_ | ConvertFrom-Json }
$unique = $events | Sort-Object Computer, RecordId, TimeUtc -Unique
$unique | Where-Object EventId -In @(4728,4732,4756) |
Select-Object TimeUtc, Computer, EventId, RecordId, Data
Не загружайте неограниченный многолетний архив целиком в память: ограничьте файлы выбранным периодом. Для критичных групп далее сравнивайте TargetSid с заранее подтверждённым SID. Удаление общего текста Message и substring имени «Admin» не является надёжным фильтром привилегий.
8. Перейдите к централизованному WEF/WEC
Для постоянного аудита рассмотрите Windows Event Forwarding с отдельным коллектором. Source-initiated subscription задаётся GPO, источники ограничиваются согласованной группой, а Security на DC настраивается отдельно. WEF собирает уже существующие события; он не включает аудит и не создаёт SACL вместо администратора.
Используйте доменную аутентификацию и утверждённые настройки WinRM. Не добавляйте TrustedHosts=* или Basic без шифрования как универсальное решение. Проверьте heartbeat источников, bookmarks, сроки хранения и подписку на нужные события. Порядок развёртывания описан в руководстве Microsoft WEF.
Минимальный пилот WEF. На выделенном доменном коллекторе выполните настройку Windows Event Collector по согласованной процедуре; команда может менять настройки службы и должна выполняться только на выбранном WEC:
wecutil qc /q
Get-Service Wecsvc, WinRM
В Event Viewer → Subscriptions создайте source-initiated подписку AVG-DC-Audit, разрешите выбранную группу компьютеров DC и задайте фильтр нужных Security Event IDs. На пилотных источниках в GPO Configure target Subscription Manager укажите:
Server=http://wec01.example.internal:5985/wsman/SubscriptionManager/WEC,Refresh=60
Адрес условный. Используется доменная Kerberos-аутентификация; не подменяйте её Basic/TrustedHosts=*. Разрешите сетевой путь к WEC только нужным источникам. Настройте чтение Security используемой службой пересылки по корпоративной политике, не выдавая ей Domain Admin и не затирая ACL журнала произвольной SDDL-строкой.
На коллекторе проверьте подписку и состояние источников:
wecutil es
wecutil gs AVG-DC-Audit /f:xml
wecutil gr AVG-DC-Audit
Get-WinEvent -LogName ForwardedEvents -MaxEvents 10 |
Select-Object TimeCreated, MachineName, Id, RecordId
На DC исследуйте Microsoft-Windows-Eventlog-ForwardingPlugin/Operational. Active у источника проверяйте вместе с новым контролируемым событием и heartbeat: наличие подписки не доказывает доставку. Параметры диагностики приведены в wecutil.
9. Проверьте тестовые изменения и реакцию
Создайте разрешённого тестового пользователя, измените атрибут в пилотной OU, добавьте и удалите членство в тестовой группе. Запишите время, учётную запись и DC, если он задан явно. Проверьте появление событий, чтение XML, выгрузку и оповещение. Затем проверьте отказ чтения одного источника: он должен отображаться как ошибка сбора.
Проверку удаления выполняйте только на заранее созданном тестовом объекте. Отсутствие события требует разбора категории, SACL и источника, а не вывода «изменений не было». Уведомление без доступных первичных записей не завершает проверку; оператор должен уметь открыть исходные материалы и сопоставить их с заявкой.
10. Зафиксируйте покрытие и возврат
Опишите домены и DC, включённые категории, объекты с SACL, фильтры, права сборщика, сроки хранения и получателей тревог. Укажите, какие изменения пока не покрыты: облачный каталог, другие домены, приложения и локальные группы серверов могут требовать отдельных источников.
Возврат включает отключение созданной задачи, восстановление именно изменённых политик и SACL по исходным копиям, с сохранением уже собранных журналов. Не очищайте Security ради отмены настройки. Принятый аудит должен обнаруживать согласованные тестовые действия, показывать потерю источника и давать достаточные данные для расследования, а не только присылать сообщение «в AD что-то изменилось».
Связанные инструкции: назначение группы AD, аудит доступа к файлам.
