На внутреннем пентесте финансовой организации SharpHound проработал 34 секунды. Ровно столько потребовалось Microsoft Defender for Identity, чтобы сгенерировать алерт "LDAP Reconnaissance" и передать тикет дежурному аналитику. Сбор графа AD пришлось останавливать, переключаться на ручные запросы через
ldapsearch и разбивать фильтры, чтобы не светить стандартные паттерны. С тех пор любой проект с разведкой Active Directory начинается для меня с разбора детектов на стороне SOC - и только потом с выбора инструмента. Потому что запустить SharpHound может любой, а вот не получить алерт через полминуты - уже ремесло.Место LDAP-разведки Active Directory в цепочке атаки
LDAP-разведка сидит между получением первичного доступа (initial access) и началом lateral movement. По MITRE ATT&CK - этап Discovery: Account Discovery: Domain Account (T1087.002). Сюда же примыкают Kerberoasting (T1558.003, Credential Access) и AS-REP Roasting (T1558.004, Credential Access) - обе техники начинаются с LDAP-запроса для поиска целевых учёток. Подробнее - в нашем подробном разборе пентест active directory.Бизнес-логика простая: атакующий получил low-priv доменные учётные данные (через фишинг, password spray, или они выданы в grey box пентесте) и теперь должен понять, куда двигаться дальше. LDAP-запросы дают полную карту: пользователи, группы, делегации, SPN, ACL-записи. Без этих данных планировать privilege escalation - стрельба вслепую.
Почему именно LDAP? Huntress хорошо это сформулировали: каждая доменная машина постоянно использует LDAP для штатной работы, один запрос может вернуть тысячи объектов с атрибутами, а ограничить LDAP нельзя - сломается сам Active Directory. Для SOC тут фундаментальная проблема: отличить легитимный LDAP-трафик от разведки атакующего технически очень тяжело.
[Применимо: внутренний пентест, grey box. Предусловия: сетевой доступ к DC (порты 389/636), валидные доменные учётные данные любого уровня привилегий.]
Мониторинг LDAP-запросов в SIEM: серверная и клиентская телеметрия
Прежде чем обфусцировать запросы, нужно понять, что именно записывает защитная сторона. Телеметрия LDAP идёт из трёх источников - и большинство организаций используют максимум один из них.
Event ID 1644 на контроллере домена
Основной источник данных о содержимом LDAP-запросов. Event 1644 записывает фильтр поиска, scope, базу поиска и время выполнения. По умолчанию он выключен - для активации нужно на каждом DC в реестре выставить значение "15 Field Engineering" в 5 по путиHKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics. NCC Group именно его рекомендует для анализа подозрительных LDAP-запросов. Microsoft предоставляет скрипт Event1644Reader.ps1, который вытягивает данные из этих событий и импортирует их в Excel для анализа нагрузки.Нюанс, который часто игнорируют: Event 1644 генерирует огромный объём данных на нагруженном DC. В крупных инфраструктурах включение этого логирования требует согласования с администраторами - SOC часто откладывает активацию, потому что боится положить production-контроллеры. И правильно боится, кстати.
Event ID 4662 и SACL-аудит объектов
Event 4662 фиксирует операции с объектами Active Directory: чтение свойств, запись, изменение ACL. Для работы нужен включённый аудит Directory Service Access и настроенные SACL на целевых объектах. NCC Group рекомендует обращать внимание на операции Write Property, Control Access, DELETE, WRITE_DAC и WRITE_OWNER. Этот механизм лежит в основе canary-аккаунтов - если на decoy-объекте настроен SACL с аудитом "Read all properties" для Everyone, любое обращение к нему порождает событие.Клиентская телеметрия: Event 30 и ETW
На стороне клиента (endpoint, откуда летит запрос) работает ETW-провайдер Microsoft-Windows-LDAP-Client. Black Lantern Security показали, как его активировать:
Код:
wevtutil set-log "Microsoft-Windows-LDAP-Client/Debug" /enabled:true /quiet:true /retention:false /maxsize:100032
Applications and Services Logs -> Microsoft-Windows-LDAP-Client/Debug и содержит информацию о том, какой процесс (PID) отправил запрос, с какого хоста и с какими параметрами. Huntress правильно подметили: серверные логи (Event 1644) показывают что произошло, а клиентские (Event 30) - кто это сделал. Для полной атрибуции нужны оба. Но тут есть подвох - Event 30 работает только для инструментов, использующих wldap32.dll. Impacket со своей Python-реализацией LDAP-клиента в этот лог просто не попадёт.Трансляция OID в логах - почему детекты BloodHound не срабатывают
Это тот момент, который упускают и авторы Sigma-правил, и большинство SOC-команд. Huntress подробно разобрали проблему: то, что инструмент атаки отправляет по сети, и то, что DC записывает в Event 1644 - это разные строки.Конкретный пример. Impacket
GetUserSPNs.py отправляет фильтр для поиска Kerberoastable-аккаунтов:
Код:
# Фильтр, отправляемый Impacket GetUserSPNs (T1558.003):
(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(servicePrincipalName=*))
# Что DC фактически записывает в Event 1644:
(&(objectCategory=person)(!(userAccountControl&2))(servicePrincipalName=*))
1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND) заменяется на символ &. Аналогично 1.2.840.113556.1.4.804 (LDAP_MATCHING_RULE_BIT_OR) превращается в |. DC транслирует matching rule OID в оператор побитового сравнения при записи события.По данным Huntress,
findDelegation.py отправляет фильтр (userAccountControl:1.2.840.113556.1.4.803:=16777216), а Event 1644 записывает (userAccountControl&16777216). Любое Sigma-правило, ищущее строку 1.2.840.113556.1.4.803 в поле фильтра Event 1644, никогда не сработает. Не потому что правило логически неверно - оно корректно описывает поведение инструмента. Но оно не учитывает трансляцию на стороне DC.Прямые последствия для обеих сторон:
Для SOC: если ваши Sigma-правила содержат OID-строки и работают с Event 1644 - проверьте, ищете ли вы OID или транслированный оператор. Правила в SigmaHQ (например,
proc_creation_win_hktl_rubeus.yml) чаще ориентированы на process creation, а не на LDAP-фильтры, именно поэтому.Для red team: трансляция OID - не баг, который можно эксплуатировать. Но она означает, что реальная поверхность детекта у́же - DC ищет побитовые операторы, а не OID-строки. Обфускация должна быть направлена на то, что фактически попадает в лог.
Обфускация LDAP-фильтров на практике
Требования к окружению
Для воспроизведения техник ниже нужны: доменная учётная запись (подойдёт любой аутентифицированный пользователь), сетевой доступ к DC на порт 389 (LDAP) или 636 (LDAPS), инструмент для ручных LDAP-запросов (ldapsearch из пакета ldap-utils на GNU/Linux, или PowerShell с модулем ActiveDirectory на Windows). Для верификации детекта - DC с включённым Event 1644 и SIEM с настроенным форвардингом.Манипуляция BaseObject, Scope и атрибутами
Стандартные сборщики типа SharpHound и RustHound используют широкие запросы: фильтр(objectClass=*) с базой поиска от корня домена и scope subtree. Это сразу выделяется на фоне штатного LDAP-трафика - легитимные приложения запрашивают конкретные OU или объекты.Техники снижения заметности:
Сужение BaseObject. Вместо запроса от
DC=corp,DC=local формируйте запросы к конкретным OU: OU=Users,DC=corp,DC=local, OU=Servers,DC=corp,DC=local. SOC-правила, срабатывающие на поиск от корня домена, промолчат. Предусловие: нужно знать структуру OU (её можно получить одним запросом к rootDSE, который не логируется в Event 1644).Замена scope.
subtree -> onelevel в нескольких последовательных запросах к разным OU. Запросов станет больше, но каждый из них выглядит менее подозрительно. Ограничение: если SOC ведёт поведенческую аналитику (volume-based), серия запросов может быть заметнее одного.Минимизация запрашиваемых атрибутов. SharpHound тянет десятки атрибутов одновременно -
member, memberOf, servicePrincipalName, msDS-AllowedToDelegateTo, nTSecurityDescriptor и прочее. Запрос с 30+ атрибутами на фоне штатного трафика (где обычно запрашивают 3-5) - аномалия, которую видно невооружённым глазом. Запрашивайте атрибуты порциями: сначала базовые (sAMAccountName, objectClass), потом целевые (servicePrincipalName).Эквивалентные фильтры. Фильтр
(objectCategory=person) можно заменить на (objectCategory=CN=Person,CN=Schema,CN=Configuration,DC=corp,DC=local) - полный DN категории вместо короткого имени. DC вернёт те же объекты, но строка фильтра в логе будет отличаться от сигнатуры инструмента.| Техника обфускации | Обходит Event 1644 (сигнатура) | Обходит Defender for Identity | Обходит canary-аккаунты | Контекст применения |
|---|---|---|---|---|
| Сужение BaseObject | Да | Частично | Нет | Внутренний пентест, grey box |
| Замена scope subtree -> onelevel | Частично | Частично | Нет | Внутренний пентест, low-and-slow |
| Минимизация атрибутов | Да | Частично | Нет | Любой внутренний сценарий |
| Эквивалентные фильтры (DN вместо CN) | Да | Нет | Нет | Внутренний пентест |
| Снижение page size + задержки | Да | Да | Нет | Long-term recon, 2+ дня |
| Wildcard в значениях атрибутов | Частично | Нет | Нет | Точечные запросы |
Работает если: SOC полагается на сигнатурные правила для LDAP-фильтров и Event 1644 включён без поведенческого анализа. Не работает если: развёрнуты canary-аккаунты с SACL-аудитом, используется Defender for Identity с ML-моделями, или SOC ведёт baseline LDAP-активности per-host.
Обход детектирования Kerberoasting и AS-REP Roasting через LDAP
Kerberoasting (T1558.003, Credential Access) начинается с LDAP-запроса для поиска учёток с непустымservicePrincipalName. Стандартный фильтр Impacket - тот самый (&(objectCategory=person)(!(userAccountControl&2))(servicePrincipalName=*)) (в транслированном виде). По данным Fidelis Security, SOC также ловит запросы с флагами DONT_REQ_PREAUTH (AS-REP Roasting, T1558.004), TRUSTED_FOR_DELEGATION и PASSWD_NOTREQD в userAccountControl.Варианты обфускации LDAP-части Kerberoasting:
Разделение фильтра на два запроса. Первый: получить список всех активных пользователей
(&(objectCategory=person)(!(userAccountControl&2))). Второй: для каждого пользователя отдельно запросить атрибут servicePrincipalName. SOC-правило, срабатывающее на комбинацию servicePrincipalName=* + userAccountControl в одном фильтре, промолчит. Ограничение: запросов станет на порядок больше, что может тригернуть volume-based детект.Замена objectCategory на objectClass. Фильтр
(objectClass=user) вместо (objectCategory=person) вернёт аналогичные результаты, но строка в логе будет другой. Sigma-правила, заточенные на objectCategory=person, не среагируют.Использование ANR (Ambiguous Name Resolution). Вместо прямого перечисления пользователей можно использовать атрибут
anr для поиска по частичному совпадению. Добавляет шум и затрудняет автоматический парсинг лога.Для AS-REP Roasting атакующий ищет аккаунты с флагом
DONT_REQ_PREAUTH (значение 4194304 в userAccountControl). Стандартный фильтр содержит (userAccountControl&4194304). Альтернатива - запросить всех пользователей с минимальным набором атрибутов и фильтровать userAccountControl уже на своей стороне. Подозрительный фильтр из лога уходит, но объём возвращённых данных растёт.Обнаружение разведки Active Directory: что SOC ловит несмотря на обфускацию
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Elastic Security 8.x+ - детекты основаны на Event 1644 и корреляции с другими источниками. Правила работают с транслированными фильтрами (побитовые операторы, а не OID). Если правило написано корректно (с учётом трансляции OID), обфускация эквивалентными фильтрами не спасёт - паттерн тот же.
Sigma-правила (SigmaHQ) - большинство правил для T1087.002 и T1558.003 ориентированы на process creation (
proc_creation_win_pua_adfind_execution.yml, proc_creation_win_setspn_spn_enumeration.yml) и файловые артефакты, а не на LDAP-фильтры. Вывод: обфускация LDAP-запросов не защитит от детекта по запуску инструмента.Canary-аккаунты - единственный механизм, который невозможно обойти обфускацией фильтра. Если canary-объект попадает в scope вашего запроса - Event 4662 сгенерируется. Единственная стратегия - вычислить, какие объекты являются ловушками. Косвенные признаки: аккаунт с SPN, но с устаревшим lastLogonTimestamp; группа с "вкусным" именем, но без реальных членов. Black Lantern Security прямо указывают, что при развёртывании canary критически важно обновлять
lastLogon - старая дата выдаёт ловушку опытному оператору.Гонку между обфускацией LDAP-фильтров и сигнатурными детектами red team выигрывает - слишком много способов переписать фильтр, сохранив семантику. Но это не значит, что разведка проходит незаметно. SOC, который комбинирует canary-аккаунты, поведенческий анализ и клиентский ETW, закрывает каналы, не зависящие от конкретных строк в фильтре.
Половина публичных Sigma-правил для LDAP-разведки ищет OID-строки в Event 1644, которые DC туда просто не записывает. По данным Huntress, это системная проблема: авторы правил не генерируют реальную телеметрию перед написанием детекта. На стороне атаки ситуация зеркальная - операторы запускают SharpHound с дефолтными параметрами и удивляются алерту через минуту. Обе стороны работают по шаблону вместо того, чтобы разобраться в механике.
Для атакующего правильный подход - разведка через ручные запросы с минимальным scope, в темпе легитимного трафика, с учётом того, что canary-аккаунт может стоять в любом OU. Для защитника - отказ от чисто сигнатурного подхода в пользу триады: canary + baseline + клиентская телеметрия.
Через пару лет зрелые SOC-команды полностью перейдут с «ищем паттерн фильтра» на «ищем аномалию профиля» - и тогда обфускация фильтров станет просто гигиеной, а не реальным evasion. Проверьте свои Sigma-правила на стенде: отправьте
GetUserSPNs.py запрос, откройте Event 1644 и посмотрите, что там записано. Если в правиле OID-строка - вы ловите фантом.
Последнее редактирование модератором: