Сбор учётных записей через LDAP на Python: пишем скрипт и сравниваем с BloodHound

75% вторжений в 2024 году начались с действительных учётных данных. Не с эксплойтов, не с фишинговых вложений — с логинов и паролей, которые атакующий нашёл или подобрал. Цифра из CrowdStrike Global Threat Report 2025, и IBM X-Force фиксирует рост таких атак на 71% год к году. Задумайтесь: три четверти всех взломов — и ни одного «хакерского» эксплойта.
На внутреннем пентесте Active Directory первый шаг — собрать учётные записи домена через LDAP и определить, какие из них уязвимы. Два основных подхода: написать Python-скрипт с библиотекой ldap3 или запустить BloodHound с коллектором SharpHound. Оба обращаются к контроллеру домена по протоколу LDAP, но решают разные задачи, дают разный уровень контроля и оставляют разные следы в логах. Разберём оба — и покажу, когда какой выбирать.
Бизнес-логика атаки: зачем нужен перебор учётных записей LDAP
LDAP (Lightweight Directory Access Protocol) — протокол для чтения и поиска объектов в каталоге Active Directory. Если проще: LDAP-запрос — как SQL-запрос к базе данных, только вместо таблиц — объекты каталога (пользователи, группы, компьютеры), а вместо столбцов — атрибуты (sAMAccountName, description, userAccountControl и сотни других).
В классификации MITRE ATT&CK (открытая база тактик и техник атак; T-коды вида T1087 — её идентификаторы) сбор учётных записей домена — сабтехника Domain Account (T1087.002) базовой техники Account Discovery (T1087), тактика Discovery. Атакующий получает полный список пользователей и ищет среди них:
- Сервисные аккаунты с SPN (Service Principal Name — идентификатор сервиса в Kerberos) — кандидаты для Kerberoasting. Суть атаки: хеш пароля сервисного аккаунта запрашивается через штатный механизм Kerberos и подбирается офлайн. Ничего нелегитимного с точки зрения протокола — но пароль утекает.
- Учётки с отключённой предварительной аутентификацией Kerberos — кандидаты для AS-REP Roasting (запрос хеша без знания текущего пароля). Встречается реже, но если встретилось — почти гарантированный результат.
- Пароли в LDAP-атрибутах — техника Credentials In Files (T1552.001, тактика Credential Access). Администраторы оставляют пароли в полях
description,comment,infoили в кастомных атрибутах. Звучит абсурдно, но на реальных пентестах находится стабильно. - Отключённые и неактивные учётки — для оценки парольной политики домена и поиска забытых аккаунтов со слабыми паролями.
Существуют готовые инструменты LDAP-разведки: ldapsearch (утилита командной строки), enum4linux (скрипт для SMB/LDAP-энумерации) и BloodHound. Но когда нужно гибко фильтровать атрибуты, парсить битовые маски, встраивать результаты в пайплайн пентеста — Python-скрипт с ldap3 даёт контроль, которого нет у готовых инструментов. Python — способ автоматизировать то, что BloodHound делает «под капотом», с полной прозрачностью на каждом шаге.
Active Directory Python скрипт: подключение и запросы через ldap3
Предпосылки: Python 3.8+, установленная библиотека (pip install ldap3), сетевой доступ к контроллеру домена на порт 389 (LDAP) или 636 (LDAPS).
Минимальный скрипт, который подключается к контроллеру домена, находит всех пользователей и определяет ключевые флаги каждого аккаунта:
from ldap3 import Server, Connection, ALL, SUBTREE
srv = Server('dc01.corp.local', port=389, get_info=ALL)
conn = Connection(srv, user='corp\\svc_audit', password='P@ssw0rd', auto_bind=True)
conn.search('dc=corp,dc=local',
'(&(objectClass=user)(objectCategory=person))',
search_scope=SUBTREE,
attributes=['sAMAccountName', 'description', 'userAccountControl'])
for entry in conn.entries:
uac_val = entry['userAccountControl'].value
if uac_val is None:
continue # атрибут отсутствует у некоторых системных объектов
uac = int(str(entry['userAccountControl']))
print(f"{entry['sAMAccountName']} | disabled={bool(uac & 0x2)} | noPreauth={bool(uac & 0x400000)}")
Что тут происходит, строка за строкой:
Server('dc01.corp.local', port=389, get_info=ALL) создаёт объект LDAP-сервера и сразу запрашивает метаинформацию о домене: naming contexts (корневые точки каталога), поддерживаемые расширения. Контроллер домена недоступен по сети — скрипт упадёт с ошибкой уже здесь.
Connection(srv, user='corp\\svc_audit', password='P@ssw0rd', auto_bind=True) выполняет LDAP bind — аутентификацию. auto_bind=True означает немедленную привязку при создании объекта. Неверный пароль — исключение LDAPBindError.
conn.search(...) — главная строка. Фильтр (&(objectClass=user)(objectCategory=person)) — LDAP-аналог SQL-запроса WHERE type='user' AND category='person'. Возвращает только реальных пользователей, исключая компьютерные учётки (у тех objectClass=computer).
Цикл по conn.entries парсит userAccountControl (UAC — битовая маска флагов аккаунта в AD, не путать с Windows User Account Control — это разные вещи, хотя аббревиатура одна). Бит 0x2 — «аккаунт отключён», бит 0x400000 — «отключена предварительная аутентификация Kerberos» (цель для AS-REP Roasting).
Где новичок обычно ошибается
Неверный base DN. Base DN (Distinguished Name — стартовая точка поиска в дереве каталога) задаёт, откуда начинать поиск. Указали dc=corp вместо dc=corp,dc=local — запрос вернёт пустой результат. Без ошибки, без предупреждения — просто ноль записей. Правильный base DN содержится в атрибуте defaultNamingContext, который ldap3 запрашивает автоматически при get_info=ALL.
Отсутствие пагинации. Контроллер домена по умолчанию возвращает максимум 1000 записей на один LDAP-запрос (лимит MaxPageSize в политике LDAP). В домене с 5000 пользователей скрипт без пагинации молча вернёт первые 1000. Решение: параметр paged_size=500 в conn.search() и обработка cookie для последующих страниц. Как понять, что попали в лимит: получили ровно 1000 записей — это не совпадение.
Права сервис-аккаунта. Обычный доменный пользователь видит большинство атрибутов, но не все. ms-Mcs-AdmPwd (пароль LAPS — Local Administrator Password Solution) требует специальных прав. На пентесте начинайте с тем, что есть: даже минимальные права дают список пользователей, членство в группах и описания.
LDAP-атрибуты учётных записей: что искать при сборе данных
Стандартные атрибуты, которые возвращает LDAP-запрос к Active Directory:
| Атрибут | Что содержит | Зачем на пентесте |
|---|---|---|
sAMAccountName |
Логин пользователя | Список целей для password spraying |
userAccountControl |
Битовая маска флагов | Отключённые учётки, аккаунты без preauth |
servicePrincipalName |
SPN сервисного аккаунта | Кандидаты для Kerberoasting |
memberOf |
Группы пользователя | Привилегированные аккаунты (Domain Admins и др.) |
pwdLastSet |
Дата последней смены пароля | Учётки со старыми паролями |
lastLogonTimestamp |
Последний вход | Неактивные аккаунты |
Но самое ценное — атрибуты, в которых паролям не место. Администраторы записывают временные пароли в description («temp pass: Summer2024!»), хранят сервисные пароли в comment или info. Некоторые организации создают кастомные атрибуты для legacy-систем. BloodHound эти поля не запрашивает — его задача другая (граф привилегий, а не грепание паролей).
Скрипт для прицельного поиска паролей в текстовых атрибутах:
SUSPECT_ATTRS = ['description', 'comment', 'info']
conn.search('dc=corp,dc=local',
'(&(objectClass=user)(objectCategory=person))',
attributes=['sAMAccountName'] + SUSPECT_ATTRS)
for entry in conn.entries:
for attr in SUSPECT_ATTRS:
val = str(entry[attr]) if (attr in entry and entry[attr].value) else ''
if any(kw in val.lower() for kw in ['pass', 'pwd', 'пароль', 'cred']):
print(f"[!] {entry['sAMAccountName']}: {attr}={val}")
Скрипт проверяет description, comment и info у каждого пользователя и выводит те, где встречаются ключевые слова. Если повезёт, увидите что-то вроде: [!] svc_backup: description=Service account, pwd: Backup2023!. Это тот слой данных, который BloodHound просто игнорирует.
BloodHound: как работает сбор данных через SharpHound
BloodHound — инструмент для визуализации путей эскалации привилегий в Active Directory. До его появления маппинг путей атак в AD требовал часов ручных LDAP-запросов и тщательного анализа — BloodHound автоматизировал это полностью.
Под капотом — двухкомпонентная система. Коллектор (SharpHound для Windows, bloodhound-python для Linux) выполняет серию LDAP-запросов к контроллеру домена: собирает пользователей, группы, компьютеры, членство в группах, организационные единицы (OU — Organizational Unit, логическая группировка объектов AD), объекты групповых политик (GPO) и записи контроля доступа (ACE — Access Control Entry, правило «пользователь X может делать Y с объектом Z»). Дополнительно коллектор обращается к каждому компьютеру домена по SMB и RPC для маппинга локальных администраторов и активных сессий.
Анализатор (Neo4j + интерфейс BloodHound) строит граф: узлы — объекты AD, рёбра — привилегии и связи. Запрос «найди кратчайший путь от пользователя X до Domain Admin» — задача теории графов, которую BloodHound решает за секунды. Именно за это его и любят.
Методы сбора bloodhound-python
Коллектор bloodhound-python (запуск: bloodhound-python -u user -p 'Password' -d corp.local -ns 10.10.10.1 -c DCOnly) поддерживает три основных метода:
- Default — широкий сбор: объекты домена + обращение к хостам для сессий и локальных админов. В тестовой лабораторной среде HackingArticles на домене с 31 пользователем Default обнаружил 3 компьютера и 53 группы за секунду — на реальных доменах с тысячами объектов время и объём будут значительно больше. Шумный, но полный.
- DCOnly — только данные с контроллера домена через LDAP, без обращения к рабочим станциям. Генерирует 7 JSON-файлов (пользователи, группы, компьютеры, контейнеры, домены, GPO, OU). Быстрый и относительно тихий. Я обычно начинаю с него.
- LoggedOn — только активные сессии: кто где залогинен. Минимальный объём данных, но критически важен для планирования lateral movement (горизонтального перемещения по сети).
Что BloodHound видит, а Python-скрипт — нет
BloodHound строит граф ACL-цепочек (DACL — Discretionary Access Control List, списки разрешений на объекты AD). Если у пользователя X есть право GenericAll на группу Y, а группа Y вложена в Domain Admins — BloodHound покажет этот путь. Воспроизвести такой анализ на чистом Python — сотни строк кода для парсинга ACE и построения графа. Можно, но зачем, когда инструмент уже написан.
Второе преимущество — маппинг сессий. BloodHound знает, на каком компьютере залогинен администратор. Это открывает вектор lateral movement к этому хосту для перехвата учётных данных из памяти.
Автоматизация пентеста Active Directory: Python-скрипт vs BloodHound
| Критерий | Python + ldap3 | BloodHound |
|---|---|---|
| Скорость старта | Нужно написать скрипт | Одна команда |
| Кастомные атрибуты | Любые, включая нестандартные | Фиксированный набор |
| Визуализация путей эскалации | Нет (текстовый вывод) | Граф shortest path до DA |
| Скрытность | Настраиваемая (единичные запросы) | Шумная (тысячи запросов за секунды) |
| Интеграция в пайплайн | Нативная (Python-объекты) | Через JSON-экспорт |
| ACL-анализ | Требует отдельной реализации | Нативно |
| Сессии пользователей | Нет (только LDAP-данные) | Да (SMB/RPC к хостам) |
Когда Python-скрипт предпочтительнее: поиск паролей в нестандартных атрибутах; контроль скорости запросов для обхода детекта; интеграция результатов в автоматизированный пайплайн; выборочная энумерация — например, только сервисные аккаунты конкретного OU.
Когда BloodHound незаменим: визуализация путей эскалации привилегий; анализ ACL-цепочек (кто может сменить пароль кому, кто может добавить себя в группу); маппинг активных сессий для планирования lateral movement; быстрая первичная разведка домена одной командой.
На практике оба инструмента дополняют друг друга: BloodHound даёт карту, Python-скрипт — данные, которых на карте нет.
Сбор пользователей домена Python-скриптом: практический блок
Предпосылки: Kali Linux (или любой Linux с Python 3.8+), установленная библиотека ldap3 (pip install ldap3), сетевой доступ к контроллеру домена, валидная доменная учётная запись.
Шаг 1. Проверь доступность LDAP. Запусти nmap -p 389,636 dc01.corp.local. Ожидаемый вывод — 389/tcp open (LDAP) или 636/tcp open (LDAPS). Оба порта закрыты — LDAP-энумерация невозможна, переходи к другим техникам (Kerberos-энумерация через kerbrute, SMB-разведка).
Шаг 2. Получи метаинформацию домена. Подключись к серверу с параметром get_info=ALL. После bind ищи defaultNamingContext — это твой base DN для всех дальнейших запросов (например, DC=corp,DC=local). Видишь несколько naming contexts — ты в лесу доменов, и для полной энумерации нужно пройти по каждому.
Шаг 3. Запроси всех пользователей. Фильтр (&(objectClass=user)(objectCategory=person)) с атрибутами sAMAccountName, userAccountControl, servicePrincipalName, description, memberOf, pwdLastSet. В домене больше 1000 пользователей — обязательно paged_size=500 в параметрах поиска, иначе неполный результат без предупреждения. Получили ровно 1000 записей — это лимит, а не совпадение.
Шаг 4. Отфильтруй интересные учётки. Парси userAccountControl побитово. Ключевые флаги: 0x2 (ACCOUNTDISABLE — аккаунт отключён), 0x10000 (DONT_EXPIRE_PASSWORD — пароль не истекает, часто у сервисных), 0x400000 (DONT_REQ_PREAUTH — кандидат для AS-REP Roasting). Проверка в Python: bool(int(uac_value) & 0x2). True — флаг установлен.
Шаг 5. Найди пароли в атрибутах и определи следующий шаг. Пройдись по description, comment, info каждого пользователя — ищи подстроки pass, pwd, пароль. По данным IBM X-Force, ежедневно в dark web появляется около 6 000 свежих учётных данных — и часть из них изначально лежала в открытом виде именно в таких атрибутах. Найденные учётки с SPN передавай в GetUserSPNs.py (impacket) для Kerberoasting. Учётки без preauth — в GetNPUsers.py для AS-REP Roasting. Пароли из атрибутов — проверяй через SMB-аутентификацию на переиспользование.
Как SOC детектирует LDAP-запросы Active Directory
Знание о детектировании — не только для blue team. Пентестеру критично понимать, какие следы оставляет его скрипт.
По данным Unit 42 (Palo Alto Networks), один из главных источников видимости для защитников — Event ID 1644 (Microsoft-Windows-ActiveDirectory_DomainService). Этот лог фиксирует «дорогие» LDAP-запросы к контроллеру домена: запросы, которые посетили или вернули большое количество записей. Нюанс: Event ID 1644 не включён по умолчанию и требует правки реестра для активации. Многие SOC-команды его не включают — и остаются слепыми.
Ключевые индикаторы аномальной LDAP-активности:
- Высокое число returned entries за короткий промежуток. Легитимный LDAP-запрос обычно запрашивает конкретный объект или небольшую группу. Запрос, возвращающий тысячи записей за одну операцию — индикатор энумерации.
- Нетипичный контекст пользователя. LDAP-запросы к доменным объектам от учётки, которая обычно не занимается администрированием AD — аномалия. SOC это видит.
- Характерные паттерны инструментов. SharpHound генерирует предсказуемый набор массовых LDAP-запросов за короткий промежуток. По данным Unit 42, многие системы детектирования фокусируются именно на этих паттернах. В репозитории SigmaHQ (открытая коллекция правил обнаружения) есть правило
win_security_account_discovery.yml, которое срабатывает на массовое перечисление аккаунтов.
Средство обхода LDAP-мониторинга — SOAPHound: инструмент, который выполняет ту же энумерацию через Active Directory Web Services (ADWS, порт 9389) вместо LDAP. ADWS использует SOAP-протокол, и традиционные LDAP-мониторы его не видят. ManageEngine подтверждает: «SOAPHound — более новая альтернатива SharpHound, которая выполняет ту же энумерацию, но через интерфейс ADWS. Для окружений, которые не мониторят ADWS-соединения, энумерация через SOAPHound практически невидима». Стоит иметь в виду — и как вектор, и как точку мониторинга.
Как снизить шум Python-скрипта
В отличие от SharpHound, который выполняет тысячи запросов за секунды, Python-скрипт настраивается на тихую работу. Запрашивай только нужные атрибуты (не *), добавляй задержку между запросами (time.sleep(0.5)), используй paged search с малым размером страницы (100–200 записей) и выполняй bind от легитимного сервис-аккаунта, а не от скомпрометированного пользовательского. Следы из логов это не уберёт, но вероятность срабатывания правил, заточенных под bulk-энумерацию BloodHound, снижается существенно. Для контекста: утёкший playbook группировки Conti (по данным ManageEngine) прямо указывает AdFind как стандартный инструмент пре-шифровальной разведки — и обнаружение AdFind стало одним из ключевых индикаторов для SOC-команд. Ваш Python-скрипт в этот паттерн не попадает.
На пентестах AD я запускаю оба инструмента. BloodHound — для карты привилегий и ACL-цепочек. Python — для прицельного поиска паролей в атрибутах, для фильтрации сервисных аккаунтов по кастомным критериям, для интеграции результатов в следующий шаг без ручного копирования между терминалами.
Типичная ошибка на старте — воспринимать BloodHound как единственный инструмент AD-разведки. Получил граф, нашёл путь до Domain Admin — работа сделана. Но 38% утечек данных связаны с кражей учётных данных (Verizon DBIR 2025), и заметная часть этих данных лежит не в ACL-цепочках, а в открытом тексте внутри LDAP-каталога. В строке description пользователя, которую администратор заполнил три года назад и забыл удалить.
Рабочий цикл, который покрывает больше, чем любой отдельный инструмент: первые 10 минут — bloodhound-python с методом DCOnly для карты. Следующие 10 — Python-скрипт на ldap3 с поиском по атрибутам. Третий проход — целевой: Kerberoasting для найденных SPN, AS-REP Roasting для учёток без preauth, валидация обнаруженных паролей через SMB. Навык написания таких скриптов — конкурентное преимущество для тех, кто переходит в ИБ из разработки или системного администрирования. Если хочешь выстроить эти навыки в систему, а не собирать обрывки по YouTube — базовый трек IB Basics делает за пару месяцев то, на что при хаотичном самообучении уходит год.
Эту тему и смежные навыки разбирают на практике в курсе «Python для пентестера» Codeby Academy.
