Автоматизация перебора учётных данных Python: скрипт для HTB Cascade вместо CrackMapExec

На HTB-машине Cascade весь путь от анонимного LDAP-дампа до доменного администратора проходит через четыре пароля, и каждый нужно найти в нестандартном месте, декодировать и проверить на переиспользование. Стандартный подход — нашёл пароль, запустил crackmapexec smb 10.10.10.182 -d cascade.local -u users.txt -p rY4n5eva --continue-on-success, посмотрел результат, повторил. Четыре раза. На одной CTF-машине это терпимо, но на реальном пентесте Active Directory, где паролей в LDAP-атрибутах, GPP-файлах и реестре набирается десятки, а парольная политика ограничивает число попыток, ручной цикл быстро упирается в потолок.
Ниже — три Python-скрипта, которые заменяют ручной CrackMapExec: вытаскивают пароль из LDAP, проверяют его через SMB и прогоняют по всем пользователям домена. Если ты пока не писал ничего на impacket — не страшно: каждый скрипт разобран по строкам.
Зачем автоматизировать валидацию учётных данных в Active Directory
В классификации MITRE ATT&CK (открытая база тактик и техник атак; T-коды вида T1110 — её идентификаторы) перебор учётных данных относится к технике Brute Force (T1110, тактика Credential Access — получение паролей и секретов). На Cascade мы имеем дело не с классическим brute force, а с поиском учётных данных в атрибутах каталога (Credentials In Files, T1552.001) и последующим password spraying — один найденный пароль проверяется по всем пользователям домена вместо перебора множества паролей на одном аккаунте.
Бизнес-логика атаки прямолинейная. Администратор сохраняет пароль в нестандартном LDAP-атрибуте — на Cascade это cascadeLegacyPwd. Атакующий анонимно читает каталог, декодирует base64-строку и получает валидную учётную запись. Один скомпрометированный аккаунт открывает доступ к SMB-шарам, где находятся новые пароли в конфигурационных файлах, и цепочка горизонтального перемещения идёт до доменного администратора. На реальных пентестах вместо cascadeLegacyPwd встречаются пароли в атрибутах description, comment, info, в GPP-файлах и скриптах SYSVOL — логика та же самая.
[Применимо: внутренний пентест, legacy AD-инфраструктура. На Cascade целевая ОС — Windows Server 2008 R2 SP1.]
CrackMapExec (сейчас развивается как NetExec) — консольная утилита для массовой проверки учётных данных по SMB, LDAP, WinRM и другим протоколам. Она решает задачу валидации «одна пара логин:пароль → один хост» одной командой. Но когда нужно вытащить пароль из нестандартного атрибута, декодировать и тут же проверить, пройти по цепочке «нашёл пароль → спрей → нашёл новый аккаунт → энумерация его шар → нашёл ещё пароль → снова спрей» или контролировать задержку между попытками под конкретный lockout threshold — CrackMapExec приходится запускать вручную на каждом шаге. Python-скрипт связывает всю цепочку в одну точку входа.
Требования к окружению
Прежде чем переходить к скриптам — убедись, что окружение готово:
- ОС: Kali Linux 2023+ или любой Linux/macOS с Python 3.8+. Windows тоже подходит, но impacket стабильнее на Linux.
- RAM: 2 ГБ минимум — скрипты легковесные, нагрузка уходит на сеть, а не на процессор.
- Сеть: активное VPN-подключение к HTB (
sudo openvpn lab.ovpn). IP-адрес Cascade — 10.10.10.182 (может отличаться в конкретной сессии). - Зависимости:
pip install ldap3 impacket. Библиотека ldap3 — Python-модуль для работы с LDAP-серверами: чтение, поиск объектов в каталоге Active Directory. Impacket — коллекция Python-классов для работы с сетевыми протоколами Windows: SMB, LDAP, Kerberos, WMI. - Права: root не нужен, скрипты работают от обычного пользователя.
LDAP-энумерация Python-скриптом: извлекаем пароль из нестандартного атрибута
Что ищем и почему это работает на Cascade
LDAP (Lightweight Directory Access Protocol) — протокол доступа к каталогу Active Directory. Через него можно запрашивать информацию о пользователях, группах и компьютерах домена. На Cascade LDAP-сервер разрешает anonymous bind (null bind — подключение без логина и пароля). Если сервер это допускает, часть каталога доступна для чтения без учётных данных. В терминах MITRE ATT&CK это Discovery: Domain Account (T1087.002) — сбор информации об учётных записях домена.
Работает если: LDAP-порт 389 открыт, anonymous bind разрешён (на Cascade — да, по данным writeup’ов HTB Cascade). Не работает если: anonymous bind отключён (AD 2012+ по умолчанию ограничивает доступ), требуется LDAP Channel Binding или обязательная подпись (LDAP Signing).
Классический подход — запустить ldapsearch -x -H ldap://10.10.10.182 -b "dc=cascade,dc=local" и грепать сотни строк вывода. Проблема: нестандартный атрибут cascadeLegacyPwd не входит в стандартную схему AD, и без целенаправленного поиска его легко пропустить среди logonCount, badPwdCount и десятков служебных полей. Python-скрипт ищет конкретные атрибуты прицельно и сразу декодирует base64.
Скрипт: подключение, поиск, декодирование
Задача: подключиться к LDAP без учётных данных, найти всех пользователей с заполненным атрибутом cascadeLegacyPwd и декодировать пароль.
from ldap3 import Server, Connection, ALL
import base64
srv = Server('10.10.10.182', get_info=ALL)
conn = Connection(srv, auto_bind=True) # anonymous/null bind
conn.search('dc=cascade,dc=local', '(objectClass=person)',
attributes=['sAMAccountName', 'cascadeLegacyPwd'])
for e in conn.entries:
pwd = e['cascadeLegacyPwd'].value
if pwd:
print(f"{e['sAMAccountName']}: {base64.b64decode(pwd).decode()}")
Что здесь происходит по шагам:
Server('10.10.10.182', get_info=ALL)создаёт объект LDAP-сервера и запрашивает метаинформацию (naming contexts, поддерживаемые расширения). Если порт 389 закрыт — скрипт упадёт с ошибкой соединения уже здесь.Connection(srv, auto_bind=True)выполняет anonymous bind. Если сервер запрещает анонимный доступ — получишь исключениеLDAPBindError.conn.search(...)ищет все объекты классаpersonв доменеcascade.localи запрашивает два атрибута:sAMAccountName(логин) иcascadeLegacyPwd(нестандартный атрибут с паролем). Именно тут Python выигрывает уldapsearch: ты явно указываешь нужные атрибуты вместо дампа всего подряд.- Цикл по
conn.entriesпроверяет, есть ли значение вcascadeLegacyPwd. Если есть — декодирует base64 и выводит пару логин:пароль.
Ожидаемый вывод:
r.thompson: rY4n5eva
Только у одного пользователя этот атрибут заполнен. Пароль rY4n5eva — точка входа в машину и первая ступень цепочки.
Python вместо CrackMapExec: валидация учётных данных через SMB
Что CrackMapExec делает при проверке пароля
Когда ты запускаешь crackmapexec smb 10.10.10.182 -d cascade.local -u r.thompson -p rY4n5eva, утилита делает три вещи: устанавливает TCP-соединение на порт 445, выполняет SMB Negotiate (согласование версии протокола) и отправляет Session Setup с логином и паролем через NTLMSSP-аутентификацию. Сервер возвращает STATUS_SUCCESS — пароль валидный. [STATUS_LOGON_FAILURE](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55) — нет. CrackMapExec оборачивает это в одну команду, но под капотом использует ту же библиотеку impacket, с которой ты сейчас будешь работать напрямую.
Скрипт: SMB-аутентификация через impacket
Работает если: SMB-порт 445 открыт, аккаунт не заблокирован, разрешена NTLM-аутентификация. Не работает если: аккаунт заблокирован (lockout threshold достигнут), включён режим «только Kerberos» (NTLMv2 отключён), SMB-порт закрыт firewall.
[Применимо: внутренний пентест. На внешнем периметре порт 445 обычно закрыт.]
Задача: проверить одну пару логин:пароль через SMB, не запуская CrackMapExec.
from impacket.smbconnection import SMBConnection
def validate_cred(host, domain, user, pwd):
try:
smb = SMBConnection(host, host, sess_port=445)
smb.login(user, pwd, domain)
smb.logoff()
return True
except Exception:
return False
Разбор по шагам:
SMBConnection(host, host, sess_port=445)— устанавливает TCP-соединение и согласовывает версию SMB. Первый аргумент — имя хоста для NetBIOS-заголовка, второй — IP-адрес назначения.smb.login(user, pwd, domain)— отправляет NTLMSSP Session Setup. Если пароль неверный, impacket выбрасываетSessionErrorс кодомSTATUS_LOGON_FAILURE.smb.logoff()— корректно закрывает сессию. При множественных проверках это важно: не забить пул соединений на сервере и не генерировать лишние алерты.
Если функция вернула True — учётные данные валидны. False — пароль неверный или аккаунт заблокирован. Чтобы отличить одно от другого на реальном пентесте, стоит ловить конкретный SessionError и анализировать errorCode, но для Cascade хватит базовой проверки.
Проверка: вызови validate_cred('10.10.10.182', 'cascade.local', 'r.thompson', 'rY4n5eva'). Ожидаемый результат — True.
Password spraying скриптом: перебор паролей Active Directory Python
Password spraying (проверка одного пароля по многим учётным записям) — противоположность brute force. Вместо «тысяча паролей → один аккаунт» работает схема «один пароль → все аккаунты». Каждому аккаунту засчитывается только одна неудачная попытка, поэтому блокировка срабатывает реже.
Работает если: парольная политика допускает достаточное число попыток перед блокировкой (lockout threshold ≥ 5) или lockout threshold = 0 (блокировка отключена). На Cascade lockout threshold = 0. Не работает если: lockout threshold = 3 и observation window короткий. Одна волна spraying заблокирует все аккаунты — потеряешь доступ и получишь массу шума в логах.
Задача: найдя пароль rY4n5eva у r.thompson, проверяем — не использует ли его кто-то ещё. Переиспользование паролей — одна из самых частых находок на пентестах AD. Перед запуском spray узнай парольную политику: crackmapexec smb 10.10.10.182 --pass-pol -u '' -p ''.
import time
users = ['r.thompson', 's.smith', 'arksvc', 'j.wakefield',
's.hickson', 'd.burman', 'j.goodhand', 'a.turnbull']
for user in users:
ok = validate_cred('10.10.10.182', 'cascade.local', user, 'rY4n5eva')
tag = '+' if ok else '-'
print(f'[{tag}] cascade.local\\{user}')
time.sleep(1) # пауза — не упираться в lockout
Ожидаемый вывод:
[+] cascade.local\r.thompson
[-] cascade.local\s.smith
[-] cascade.local\arksvc
[-] cascade.local\j.wakefield
[-] cascade.local\s.hickson
[-] cascade.local\d.burman
[-] cascade.local\j.goodhand
[-] cascade.local\a.turnbull
Валидный — только r.thompson. Пароль не переиспользуется, и для дальнейшего продвижения по Cascade нужны другие источники учётных данных: VNC-файл в SMB-шаре (пароль s.smith зашифрован DES-CBC с известным ключом) и .NET-бинарь в шаре Audit$ (пароль arksvc зашифрован AES с захардкоженным ключом и IV). Каждый следующий шаг — та же логика: нашёл, декодировал, проверил спреем.
time.sleep(1) — пауза в одну секунду между попытками. На Cascade lockout отключён, и пауза некритична, но на реальных пентестах это привычка, которая спасает от массовой блокировки. Для чувствительных сред увеличивай до 5–10 секунд или рассчитывай по формуле: observation_window / (lockout_threshold - 1).
CrackMapExec или Python-скрипт: когда что использовать
Автоматизация пентеста на Python — не замена CrackMapExec, а расширение его возможностей. Конкретные критерии выбора:
| Критерий | CrackMapExec / NetExec | Python-скрипт |
|---|---|---|
| Разовая проверка пароля | Одна команда | Избыточно |
| Поддержка протоколов | SMB, LDAP, WinRM, MSSQL, SSH, RDP | То, что написал |
| Кастомная логика (найди → декодируй → проверь) | Нет | Полная |
| Парсинг нестандартных атрибутов | Через модули, частично | Любой формат |
| Интеграция с отчётностью | Вывод в файл | Нативно: JSON, CSV, БД |
| Контроль задержки между попытками | Флаг —jitter | Полный: time.sleep, random |
| Сигнатура в логах сервера | Известный User-Agent | Контролируемый |
| Порог входа | Низкий — man page | Средний — знание Python + impacket |
Когда CrackMapExec достаточен: разовый спрей по списку, быстрая проверка одной пары, перечисление шар и прав. Инструмент отполирован сообществом и покрывает 80% типовых задач.
Когда нужен Python-скрипт: на пентесте нашлось 15 паролей в разных форматах (base64, hex, AES-зашифрованные) и нужно декодировать каждый и тут же проверить по домену за одно нажатие Enter. Или нужно соблюдать строгий timing под lockout и логировать каждую попытку с временными метками для отчёта заказчику.
Когда автоматизация перебора не работает
Три сценария, в которых показанный подход потребует доработки:
- Kerberos-only auth. Если на домене отключён NTLM,
SMBConnection.login()с паролем не пройдёт. Нужно переключиться на получение TGT черезimpacket.krb5.kerberosv5.getKerberosTGT()и использовать Kerberos-аутентификацию. На modern-инфраструктурах (Windows Server 2019+, Defender for Identity) это встречается всё чаще. - Network segmentation. Если между атакующей машиной и DC стоит файрвол, блокирующий порты 445 и 389, скрипт не сможет подключиться. Нужен pivoting через уже скомпрометированный хост — но это отдельная задача.
- Современные средства обнаружения. На инфраструктурах с Defender for Identity или аналогичными решениями массовые LDAP-запросы и серия SMB-аутентификаций генерируют алерты. Скрипт позволяет контролировать timing и объём запросов, но полностью скрыть spraying от зрелого SOC (Security Operations Center — центр мониторинга безопасности) не получится.
На Cascade все три сценария неактуальны: legacy-инфраструктура на Windows Server 2008 R2 без EDR и сетевой сегментации. Именно поэтому машина хорошо подходит для отработки базовой автоматизации — но переносить скрипты на боевой пентест нужно с поправкой на защитные механизмы.
Весь цикл «LDAP → base64 → SMB spray» на Cascade укладывается в 25 строк Python. CrackMapExec потребовал бы три отдельные команды с промежуточным echo clk0bjVldmE= | base64 -d в терминале и ручным копированием результата. Разница невелика на одной машине, но на пентесте с десятью источниками паролей Python экономит часы.
Я намеренно показал простейшие скрипты без обработки ошибок, логирования и параллельных потоков. На реальном пентесте стоит добавить: обработку SessionError с разбором кодов (STATUS_ACCOUNT_LOCKED_OUT — это не то же самое, что STATUS_LOGON_FAILURE), запись результатов в JSON-файл и поддержку NTLM-хешей (pass-the-hash) через параметр nthash в smb.login(). Всё это — расширения базовой логики, описанной выше.
И напоминание, которое стоит зафиксировать: любые действия — и CrackMapExec, и собственный скрипт — выполняются только в рамках авторизованного тестирования: HTB, собственная лаба, пентест с письменным разрешением.
Я вижу устойчивый паттерн: начинающие пентестеры учат CrackMapExec как «инструмент для спрея» и Hydra как «инструмент для brute force», но не понимают, что оба делают одно и то же — отправляют запрос аутентификации по сетевому протоколу и проверяют ответ. Пока не напишешь этот цикл сам — connect → login → check status → next — инструменты остаются чёрными ящиками, а ты остаёшься оператором. Оператор упирается в потолок быстро: не может адаптировать атаку под нестандартную среду, не может объяснить заказчику, что именно проверялось, не может автоматизировать цепочку дальше первого шага. На каждом втором пентесте AD встречается ситуация, где готовый инструмент «не подходит» — кастомный формат пароля, нестандартный порт, необходимость параллельно логировать в SIEM-совместимый формат. Python решает это за 20 минут. На IB Basics в Codeby Academy показывают не теорию, а как джуны реально решают подобные рабочие задачи — если нужна системная база для таких сценариев.
Эту тему и смежные навыки разбирают на практике в курсе «Python для пентестера» Codeby Academy.