Перейти к содержимому

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

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

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.