Перейти к содержанию
Обсудить задачу →
Статьи и инструкции

Аудит Active Directory в PowerShell: изменения, события и оповещение

Как настроить аудит Active Directory: GPO, SACL, события всех DC, XML и JSONL, сигналы мониторинга, ошибки сбора и централизованный WEF.
Иллюстрация к инструкции: Аудит Active Directory в PowerShell: изменения, события и оповещение

Уведомление об изменениях 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 означает неполный сбор и должен создавать отдельную тревогу.

Аудит AD: категории GPO, SACL, события всех контроллеров, сбор XML и проверяемое оповещение
Порядок проверки и настройки. Команды, ограничения и диагностика приведены в статье.

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, аудит доступа к файлам.