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

Как я написал на Python скрипт для проверки паролей Active Directory через связку с Impacket и что из этого пригодилось на пентесте

Как я написал на Python скрипт для проверки паролей Active Directory через связку с Impacket и что из этого пригодилось на пентесте
Время чтения: 12 мин.

На внутреннем пентесте промышленной компании я запустил CrackMapExec для проверки учёток — через 40 секунд 14 аккаунтов оказались заблокированы. Проект встал на сутки. Администратор разлочивал учётные записи, безопасники писали объяснительную руководству, а я сидел и переосмыслял подход к password spraying. Если вы только входите в тему — password spraying это техника, при которой один пароль проверяется по большому списку учёток, в отличие от брутфорса, где много паролей перебираются для одного аккаунта. По данным CrowdStrike Global Threat Report 2025, 75% вторжений используют действительные учётные данные, а IBM X-Force Threat Intelligence Index 2025 фиксирует около 6000 свежих утечек учёток в даркнете ежедневно. Password spraying — один из способов, которыми атакующие конвертируют эту массу утечек в первоначальный доступ. После того инцидента я написал собственный скрипт на Python поверх Impacket — и ниже полный разбор, как он устроен и где помог.

Зачем python для пентестера, когда есть готовый инструментарий

CrackMapExec (сейчас переименован в NetExec), Sprayhound, Kerbrute — все умеют password spraying из коробки. Проблема не в функциях, а в контроле. CrackMapExec по умолчанию не запрашивает lockout policy домена. Lockout policy — набор правил Active Directory, определяющих, после скольких неудачных попыток учётка блокируется и на какое время. Инструмент перебирает со скоростью сети. Для учебной машины — нормально. Для прод-домена с 2000 пользователей — катастрофа.

Три сценария, в которых кастомный скрипт побеждает готовое решение:

Жёсткая lockout policy. Порог блокировки — 3 попытки за 30 минут. Одна ошибка в настройке паузы — проект останавливается. В своём скрипте контроль явный: сколько попыток на учётку и какой интервал между раундами задаёте вы сами.

Нестандартный протокол. На части пентестов порт 445 (SMB — протокол обмена файлами Windows) закрыт фаерволом, но открыт LDAP (протокол доступа к каталогу, порт 389) или Kerberos (протокол аутентификации, порт 88). Готовый инструмент чаще привязан к одному протоколу. В своём скрипте достаточно подменить один модуль Impacket.

Интеграция с разведкой. Когда список учёток приходит из BloodHound (инструмент визуализации атакующих путей в AD) или из LDAP-дампа, нужно фильтровать прямо в коде: исключать заблокированные аккаунты, приоритизировать сервисные учётки с SPN. SPN (Service Principal Name) — маркер, указывающий, что учётка привязана к сервису. Такие учётки интересны отдельно: их можно атаковать через Kerberoasting. Готовые инструменты этого не дают без дополнительных обёрток.

Impacket — Python-библиотека для работы с сетевыми протоколами Windows: SMB, LDAP, Kerberos, MSRPC. В базе MITRE ATT&CK (открытый каталог тактик и техник атак — если ещё не знакомы, стоит изучить, он используется повсеместно) Impacket фигурирует как софт S0357 для credential dumping, lateral movement и Kerberos-манипуляций. Библиотека предустановлена в Kali Linux, на любом другом дистрибутиве ставится через pip3 install impacket (в venv проекта). Берите версию ≥ 0.12.0 — более ранние содержат path traversal GHSA-mj63-64×7-57xf. На Kali скрипты переименованы с префиксом impacket- (например, secretsdump.pyimpacket-secretsdump) — это часто сбивает с толку при первом запуске.

Как работает блокировка учёток в Active Directory

Прежде чем писать sprayer, разберитесь с механикой счётчика неудачных попыток. Это не просто «N неверных паролей = блокировка» — есть нюансы, от которых зависит, пройдёт пентест гладко или окажется на паузе.

Lockout policy в AD определяется тремя параметрами:

  • Account Lockout Threshold — количество неудачных попыток до блокировки. Значение 0 означает, что блокировка отключена. Для пентестера — подарок.
  • Account Lockout Duration — время блокировки в минутах. Если 0 — разлочить может только администратор вручную. Именно это превращает ошибку в инцидент.
  • Reset Account Lockout Counter After — через сколько минут счётчик неудачных попыток обнуляется при отсутствии новых ошибок.

Момент, который критичен для автоматизации пентеста AD: счётчик сбрасывается после успешной аутентификации ИЛИ по истечении reset-интервала. Если порог — 5 попыток, а сброс — 30 минут, можно безопасно делать 2–3 попытки на учётку каждые 30 минут. SmartSpray (проект password spraying поверх Impacket на GitHub) реализует похожую логику: ограничивает количество попыток за цикл, оставляя запас до порога блокировки.

Получить параметры lockout policy можно одной командой: crackmapexec smb <DC_IP> -u user -p pass --pass-pol. В выводе будут строки Account Lockout Threshold, Reset Account Lockout Counter и Account Lockout Duration. Если CrackMapExec недоступен — тот же результат даёт ldapsearch с фильтром (objectClass=domain) и атрибутами lockoutThreshold, lockoutDuration, lockoutObservationWindow. Значение lockoutDuration приходит в формате Windows FILETIME — отрицательное число в 100-наносекундных интервалах. Значение 0 — бессрочная блокировка (только ручное разблокирование), формулу применять не нужно. В остальных случаях, чтобы перевести в минуты, делите абсолютное значение на 600 000 000.

Что проверить до первого запуска

Перед запуском password spraying python-скрипта:

  1. Убедитесь, что разведочная учётка активна и не expired. Проверка — ldapsearch с фильтром по атрибуту userAccountControl.
  2. Зафиксируйте lockout threshold. Если он равен 0 — ограничений нет. Если 3 — работайте максимально аккуратно, не более одной попытки за reset-интервал.
  3. Проверьте наличие Fine-Grained Password Policy — отдельных политик для разных групп. Они перекрывают доменную политику и бывают жёстче. На Windows: Get-ADFineGrainedPasswordPolicy -Filter *, из Linux — LDAP-запрос к контейнеру CN=Password Settings Container. Пропустите этот шаг — и на группе «Бухгалтерия» может оказаться порог в 3 попытки, хотя доменный — 10.

Password spraying на Python с Impacket: ядро скрипта

Где password spraying находится в цепочке атаки: это этап Initial Access. Получив валидные учётные данные, пентестер переходит к разведке (LDAP-dump, BloodHound), затем к Kerberoasting, privilege escalation и lateral movement. Один удачный пароль может открыть путь до Domain Admin.

Модуль impacket.smbconnection позволяет открыть SMB-сессию с хостом и проверить аутентификацию. Неверный пароль — исключение с конкретным кодом ошибки. Верный — сессия открывается. На этом строится вся логика.

Ядро функции проверки одной пары «логин:пароль». Предполагается Impacket ≥ 0.12.0 и Python 3.8+:

from impacket.smbconnection import SMBConnection, SessionError

def try_login(target, domain, username, password):
    try:
        conn = SMBConnection(target, target, timeout=5)
        conn.login(username, password, domain)
        conn.logoff()
        return True   # пароль подошёл
    except SessionError as e:
        code = e.getErrorCode()
        if code == 0xc000006d:   # STATUS_LOGON_FAILURE
            return False
        if code == 0xc0000234:   # STATUS_ACCOUNT_LOCKED_OUT
            return None
        if code in (0xc0000072,  # STATUS_ACCOUNT_DISABLED
                     0xc0000071,  # STATUS_PASSWORD_MUST_CHANGE
                     0xc000006e): # STATUS_ACCOUNT_RESTRICTION
            return False  # учётка непригодна, но не блокировка
        raise

Разберём по шагам. SMBConnection открывает TCP-соединение на порт 445 целевого хоста. Метод login() отправляет NTLM-аутентификацию — это протокол challenge-response, который Windows использует для проверки пароля без передачи его в открытом виде. При верном пароле метод возвращает управление, conn.logoff() корректно закрывает сессию. При неверном — Impacket выбрасывает SessionError с кодом STATUS_LOGON_FAILURE (0xc000006d). Если учётка уже заблокирована — код будет STATUS_ACCOUNT_LOCKED_OUT (0xc0000234), и это сигнал немедленно прекратить попытки для этого аккаунта.

Обвязка — цикл, который берёт один пароль, проходит по списку учёток, выжидает интервал и берёт следующий:

import time, random

def spray(target, domain, users, passwords, wait_min=35):
    for pwd in passwords:
        print(f"[*] Раунд: пароль '{pwd}'")
        for user in list(users):
            result = try_login(target, domain, user, pwd)
            if result is True:
                print(f"[+] НАЙДЕН: {user}:{pwd}")
            elif result is None:
                users.remove(user)
            time.sleep(random.uniform(0.5, 2.0))
        print(f"[*] Пауза {wait_min} мин")
        time.sleep(wait_min * 60)

Обратите внимание на внешний цикл — он идёт по паролям, не по учёткам. В этом суть password spraying: один пароль проверяется по всем учёткам, потом следующий. Между каждой попыткой — случайная задержка от 0.5 до 2 секунд (jitter), чтобы трафик не выглядел как машинный burst. Между раундами — пауза wait_min минут, которая должна превышать reset-интервал lockout policy. Если try_login вернул None (учётка заблокирована) — она исключается из дальнейших попыток.

Скрипт выше — минимальное рабочее ядро. На реальных проектах я дополнил его несколькими механизмами, без которых на прод-домене делать нечего.

Рандомизация hostname. Приём, который я подсмотрел в SmartSpray: при каждом SMB-соединении передавать случайное имя хоста через параметр myName в SMBConnection. Вместо одного и того же имени машины в логах DC появляются разные — DESKTOP-7X91D2, DESKTOP-M4KT9F — и растворяются среди реальных рабочих станций домена. Генерация простая: DESKTOP- + 6 случайных символов перед каждым вызовом.

Фильтрация по сложности. Если доменная политика требует минимум 8 символов и 3 из 4 классов (uppercase, lowercase, цифра, спецсимвол), проверять пароль 123 бессмысленно. Фильтр экономит время и снижает количество неудачных попыток — а каждая неудачная попытка приближает к порогу блокировки. SmartSpray реализует четыре уровня фильтрации: от отсутствия проверки до строгой GPO-совместимой валидации с проверкой вхождения имени пользователя в пароль.

Сохранение состояния. VPN отвалился, SSH-сессия оборвалась — скрипт должен продолжить с того места, где остановился. Простейший вариант: запись прогресса в JSON-файл после каждого раунда с номером текущего пароля и списком уже проверенных учёток. Без этого механизма я однажды потерял 4 часа работы из-за обрыва VPN.

Логирование с таймстампами. На пентесте это не опция, а требование. Заказчик запросит точное время каждой попытки для корреляции со своими логами. Каждый вызов try_login — строка в лог-файле с временной меткой, именем пользователя и результатом.

Переключение протокола. Если SMB закрыт — заменяем SMBConnection на Kerberos-аутентификацию через impacket.krb5.kerberosv5.getKerberosTGT. Структура скрипта (цикл по паролям → цикл по учёткам → пауза) сохраняется, но меняется не только транспорт: вместо SessionError обрабатывается KerberosError с другими кодами (KDC_ERR_PREAUTH_FAILED, KDC_ERR_CLIENT_REVOKED), а вместо NetBIOS-имени домена требуется FQDN realm. На одном проекте порт 445 был закрыт для всех подсетей, кроме серверной, зато Kerberos (порт 88) был доступен отовсюду. Переключение заняло 15 минут и позволило продолжить работу без запроса к заказчику на открытие порта.

Делай раз, делай два, делай три: запуск на стенде

Прежде чем применять скрипт на реальном домене, обкатайте его на тестовом стенде. Ниже — пошаговый порядок.

Предпосылки: Kali Linux (или любой Linux с Python 3.8+), установленный Impacket, сетевой доступ до контроллера домена на порт 445, хотя бы одна валидная доменная учётка уровня Domain Users.

Шаг 1. Получите lockout policy. Выполните crackmapexec smb <DC_IP> -u <user> -p <pass> --pass-pol. В выводе ищите Account Lockout Threshold и Reset Account Lockout Counter. Ожидаемый результат: строки вида Lockout Threshold: 5 и Lockout Observation Window: 30 mins. Threshold = 0 — блокировки нет. Threshold = 5 при reset = 30 — ставьте wait_min=35 в скрипте.

Шаг 2. Подготовьте список учёток. Запустите impacket-GetADUsers -all -dc-ip <DC_IP> domain/user:pass. Команда выведет таблицу с полями Name, Email, PasswordLastSet, LastLogon. Сохраните имена в users.txt.

Шаг 3. Подготовьте список паролей. Начните с 3–5 кандидатов по паттернам: Сезон+Год (Summer2025!, Winter2024!), Компания+Цифры (Company123), стандартные вроде Welcome1, P@ssw0rd. Отфильтруйте те, что не проходят по длине и сложности доменной политики.

Шаг 4. Запустите скрипт. Следите за строками [+] НАЙДЕН — это валидные учётные данные. Для 100 учёток и 5 паролей с паузами 35 минут между раундами общее время — около 3 часов. Каждый раунд (100 учёток с jitter 0.5–2 секунды) занимает 1–3 минуты.

Шаг 5. Используйте результаты. Валидная пара «логин:пароль» — входная точка для следующих этапов: impacket-GetUserSPNs для Kerberoasting (извлечение тикетов сервисных учёток для офлайн-перебора), impacket-secretsdump для дампа хешей при наличии административных прав, запуск BloodHound для построения пути до Domain Admin.

Что увидит SOC и как обнаружить password spraying

Для защитников — и для понимания, насколько ваш скрипт заметен — полезно знать, какой след оставляет password spraying в логах. MITRE ATT&CK классифицирует эту технику как T1110.003 (тактика Credential Access). Если вы ещё не работали с ATT&CK — T-коды это идентификаторы конкретных техник атак в базе, и на них ссылаются почти все SOC-отчёты.

Password spraying генерирует массу событий Windows Security Event Log с ID 4625 (неуспешный вход). Характерный паттерн: много событий 4625 от разных учётных записей, но с одного IP-источника за короткий интервал. При spraying через Kerberos — событие 4771 (Kerberos Pre-Authentication Failed). SOC-команды детектируют не одиночный промах, а аномалию: 50 событий 4625 за 2 минуты от одного источника — красный флаг.

Хороший sprayer с jitter и длинными паузами делает паттерн менее очевидным, но не невидимым. На контроллере домена с включённым расширенным аудитом каждый вызов SMBConnection.login() оставляет запись с полным NTLM-контекстом, включая Workstation Name. Вот почему рандомизация hostname из предыдущего раздела имеет значение.

Для смежной техники — Name Resolution Poisoning and SMB Relay (T1557.001) — в репозитории SigmaHQ (коллекция открытых правил обнаружения, если не знакомы — стоит добавить в закладки) есть готовые правила: proc_creation_win_hktl_relay_attacks_tools.yml детектирует запуск relay-инструментов по паттернам процесса, zeek_dce_rpc_potential_petit_potam_efs_rpc_call.yml ловит EFS RPC вызовы в PetitPotam-атаках. Для самого password spraying Sigma-правило строится на корреляции множества 4625 по источнику — написать его несложно.

Защитные меры по MITRE D3FEND (knowledge graph контрмер — ещё один полезный ресурс от MITRE, но уже для blue team) для credential access включают Connection Attempt Analysis (D3-CAA) — мониторинг паттернов подключений — и Network Traffic Community Deviation (D3-NTCD) — выявление аномального сетевого трафика. На практике: если в домене 200 рабочих станций, и вдруг с одного IP за 5 минут идёт 200 SMB-аутентификаций — D3-NTCD сработает даже без специальных правил, если мониторинг настроен.

Большинство пентестеров, которых я знаю, относятся к скриптам как к одноразовым поделкам: написал, отработал, забыл. Я так делал первые полтора года. Потом обнаружил, что трачу по 2–3 часа на каждом проекте, заново решая одни и те же проблемы — lockout-контроль, фильтрацию учёток, парсинг результатов. Этот скрипт для password spraying на Python через Impacket стал первым инструментом, который я переиспользовал на пяти проектах подряд без переписывания.

Суть не в том, что готовые инструменты плохие — CrackMapExec отлично работает в 80% случаев. Оставшиеся 20% — ситуации с жёсткими lockout-порогами, нестандартными протоколами или интеграцией с разведданными — определяют разницу между пентестером, который пишет «password spraying не применялся из-за risk of lockout» в отчёте, и пентестером, который находит валидные учётки и доводит проект до конца.

Умение читать исходники Impacket и собирать поверх них свои инструменты — навык, который отделяет оператора готового софта от специалиста, способного адаптироваться к любой среде. На курсе WAPT эту цепочку — от разведки до password spraying и дальше до lateral movement — проходят в нескольких модулях с лабами, где можно безопасно заблокировать все учётки домена и разобраться, что пошло не так.

Эту тему и смежные навыки разбирают на практике в курсе «Python для пентестера» Codeby Academy.