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

AS-REP Roasting атака: пишем Python-парсер хешей Kerberos вместо impacket

AS-REP Roasting атака: пишем Python-парсер хешей Kerberos вместо impacket
Время чтения: 11 мин.

На HTB-машине Cicada первый AS-REP хеш достался за три минуты — impacket-GetNPUsers выплюнул строку $krb5asrep$23$, hashcat подобрал пароль из rockyou за секунды. Цепочка отработана. Но потом я завис на вопросе: из каких именно байтов ответа контроллера домена собирается эта строка? Стандартный инструмент не покажет — он работает как чёрный ящик. Пришлось написать парсер с нуля на Python, разобрать AS-REP по полям и собрать hashcat-строку руками. Что из этого вышло — ниже.

Что происходит внутри AS-REP на уровне протокола Kerberos

Kerberos — протокол аутентификации в Active Directory (AD — служба каталогов Microsoft, через которую управляются учётные записи и права доступа в корпоративных Windows-сетях). Каждый раз при входе пользователя его машина обменивается сообщениями с контроллером домена (DC — сервер, который хранит учётные записи и выдаёт аутентификационные билеты).

Нормальная последовательность аутентификации:

  1. Клиент отправляет AS-REQ (Authentication Service Request). Внутри — временная метка, зашифрованная хешем пароля пользователя. Это pre-authentication: доказательство знания пароля до получения билета.
  2. DC расшифровывает timestamp своей копией хеша. Совпало — пользователь подтвердил личность.
  3. DC отправляет AS-REP (Authentication Service Response) с двумя компонентами: — TGT (Ticket Granting Ticket — «пропуск» для дальнейшего доступа к ресурсам домена), зашифрованный ключом учётной записи krbtgt. Расшифровать его может только DC, для офлайн-подбора пароля он бесполезен. — enc-part — данные (включая сессионный ключ), зашифрованные хешем пароля пользователя. Вот эту часть можно вскрыть офлайн-подбором.

Когда у учётной записи стоит флаг «Do not require Kerberos preauthentication» (DoesNotRequirePreAuth), DC пропускает проверку timestamp. Он отдаёт AS-REP любому, кто назовёт имя пользователя — без доказательства знания пароля. Шифрование enc-part никуда не девается, но теперь атакующий получает зашифрованные данные без ограничений и подбирает пароль офлайн с любой скоростью.

MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1558.004 — идентификаторы конкретных техник) классифицирует это как T1558.004 — AS-REP Roasting в тактике Credential Access.

Зачем атакующему этот вектор? Монетизация прямая: получив пароль доменной учётки, можно двигаться дальше по сети — к файловым шарам, базам данных, другим сервисам. Если учётка привилегированная (а legacy-аккаунты с отключённой pre-authentication часто такими бывают) — путь до Domain Admin может оказаться коротким.

Структура AS-REP: где лежат нужные байты

AS-REP закодирован в формате ASN.1 (Abstract Syntax Notation One — стандарт описания структурированных данных; используется в Kerberos, TLS-сертификатах и десятках других протоколов). Для парсера критичны три поля внутри enc-part:

  • etype — алгоритм шифрования. Значение 23 означает RC4-HMAC. Инструменты AS-REP Roasting запрашивают именно RC4, потому что он крекается на порядки быстрее AES.
  • cipher — зашифрованные данные. Из этого массива байтов собирается строка для hashcat.
  • kvno — версия ключа (Key Version Number), опциональное поле EncryptedData (RFC 4120), может отсутствовать в ответе. Для парсинга хешей не критична, но при отладке пригодится.

Зачем понимать эту структуру: impacket-GetNPUsers разбирает ASN.1 за вас, извлекает cipher и форматирует результат. Когда вы пишете свой скрипт — делаете ровно то же самое, но контролируете каждый шаг. На реальных пентестах это пригождается чаще, чем кажется.

AS-REP Roasting на HTB: стандартный путь через GetNPUsers

Прежде чем строить свой парсер, покажу стандартный подход — чтобы было с чем сравнить результат.

Предпосылки: Kali Linux с установленным Impacket (набор Python-скриптов для работы с сетевыми протоколами Windows; в Kali стоит по умолчанию), активное VPN-подключение к HTB, доступ к порту 88/TCP контроллера домена.

Вся AS-REP Roasting атака через готовый инструмент — две команды. Получение хеша: impacket-GetNPUsers -dc-ip 10.10.11.35 -usersfile users.txt -format hashcat -outputfile hashes.txt cicada.htb/. Скрипт отправляет AS-REQ для каждого имени из файла users.txt. Если pre-authentication отключена, DC возвращает AS-REP — скрипт извлекает cipher и сохраняет в формате hashcat mode 18200. Ожидаемый вывод при успехе — строка $krb5asrep$23$username@DOMAIN:...длинный_хеш.... Пустой вывод = ни одна учётка из списка не уязвима.

Подбор пароля: hashcat -m 18200 hashes.txt /usr/share/wordlists/rockyou.txt. Флаг -m 18200 указывает режим Kerberos 5 AS-REP etype 23 (RC4). На GPU класса GTX 1080 пароли из словаря rockyou.txt (14 млн записей) подбираются за секунды. Без GPU — john --wordlist=rockyou.txt --format=krb5asrep hashes.txt, но существенно медленнее.

Результат есть, пароль подобран. Но что внутри строки $krb5asrep$23$? Откуда берутся числа после двоеточия? GetNPUsers не покажет. Разберём руками.

Python для пентестера: парсер хешей Kerberos вместо GetNPUsers

Зачем писать альтернативу impacket

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

  1. Обучение протоколу. GetNPUsers — чёрный ящик: передаёшь имя, получаешь hashcat-строку. Что между — непрозрачно. Написать парсер = понять, откуда каждый символ.
  2. Нестандартный output. На одном пентесте нужно было извлечь etype и realm из сотен AS-REP ответов для статистики по домену. GetNPUsers такого не выдаёт, а скрипт на 40 строк решил задачу.
  3. Модификация запроса. Отправка AS-REQ с нестандартными etype (только AES-128 вместо RC4) или специфичными PA-DATA требует правки исходников impacket — GetNPUsers не параметризует etype через CLI. Свой скрипт проще адаптировать.

Как hashcat формат 18200 собирается из AS-REP

Прежде чем смотреть код — разберём, что означает каждая часть hashcat-строки. Формат mode 18200:

$krb5asrep$23$user@DOMAIN:checksum_hex$edata2_hex

Компоненты: — $krb5asrep$ — маркер протокола (Kerberos 5, AS-REP). — 23 — etype, тип шифрования (23 = RC4-HMAC). — user@DOMAIN — имя учётной записи и домен. — checksum_hex — первые 16 байт поля cipher из enc-part AS-REP, переведённые в hex. Hashcat использует их как контрольную сумму при проверке кандидатов. — edata2_hex — оставшиеся байты cipher (зашифрованные данные), тоже в hex.

Задача парсера: достать cipher из ASN.1-структуры, разрезать на две части и собрать строку по шаблону.

Разбираем AS-REP и собираем hashcat-строку

Ядро парсера на Python. Он принимает сырые байты AS-REP (полученные через сокет или извлечённые из pcap) и возвращает готовую hashcat-строку:

from pyasn1.codec.der import decoder
from pyasn1.type import univ, tag
import binascii
# Для надёжного парсинга рекомендуется передавать asn1Spec (например, из impacket.krb5.asn1 или pyasn1-modules).
# Без явной схемы decoder.decode() вернёт generic Any-объекты, и getComponentByPosition может работать некорректно
# из-за implicit/explicit tagging в Kerberos ASN.1. Пример ниже — упрощённый, для продакшена используйте asn1Spec=.
def parse_asrep(raw_bytes, username, domain):
    asrep, _ = decoder.decode(raw_bytes)  # без asn1Spec — generic decode, см. примечание выше
    enc_part = asrep.getComponentByPosition(6)   # EncryptedData (tag [6] per RFC 4120 §5.4.2), содержит etype[0], kvno[1], cipher[2]
    etype = int(enc_part.getComponentByPosition(0))   # 23 = RC4
    cipher = bytes(enc_part.getComponentByPosition(2))
    checksum = binascii.hexlify(cipher[:16]).decode()
    edata2 = binascii.hexlify(cipher[16:]).decode()
    return f"$krb5asrep${etype}${username}@{domain}:{checksum}${edata2}"

Что происходит на каждом шаге:

  • decoder.decode(raw_bytes) — библиотека pyasn1 ([pip install pyasn1](https://pypi.org/project/pyasn1/)>=0.7.0; более ранние версии подвержены DoS-уязвимостям — GHSA-8ppf-4f7h-5ppj (fixed 0.6.1), GHSA-hm4w-wwcw-mr6r (fixed 0.7.0), GHSA-63vm-454h-vhhq (fixed 0.6.1); перед использованием проверьте актуальный CVE-статус пакета) разбирает DER-кодированный ASN.1-пакет в Python-объект. Каждый элемент доступен по индексу. Индексы в примере зависят от используемой ASN.1-схемы — при работе без готовой Kerberos-схемы может потребоваться адаптация позиций.
  • enc_part.getComponentByPosition(0) — извлекает etype. Если значение 23 — шифрование RC4, можно продолжать. Значения 17 (AES-128) или 18 (AES-256) тоже встречаются, но hashcat для них использует другие режимы (19600 и 19700). Индекс getComponentByPosition(6) в parse_asrep соответствует context tag [6] enc-part в структуре KDC-REP (RFC 4120 §5.4.2); при использовании pyasn1 без явной ASN.1-схемы Kerberos фактическая позиция может отличаться — проверяйте на реальном AS-REP.
  • cipher[:16] — первые 16 байт шифротекста hashcat трактует как HMAC-контрольную сумму. Остаток (cipher[16:]) — зашифрованные данные.
  • Финальная строка собирается конкатенацией по шаблону hashcat mode 18200.

Как проверить результат: запустите GetNPUsers для того же пользователя и сравните выходные строки. Они должны совпасть посимвольно — обе функции извлекают одни и те же байты из одного и того же AS-REP.

Получение сырого AS-REP через сокет

Если нужно не только парсить, но и самостоятельно отправить AS-REQ на DC (полностью заменить GetNPUsers), понадобится сетевое взаимодействие. Kerberos работает на порту 88/TCP, framing тривиален: 4 байта длины + payload.

import socket
def send_asreq(dc_ip, asreq_bytes):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((dc_ip, 88))
    length = len(asreq_bytes).to_bytes(4, 'big')  # Kerberos TCP framing
    sock.sendall(length + asreq_bytes)
    resp_len = int.from_bytes(sock.recv(4), 'big')
    asrep_raw = b''
    while len(asrep_raw) < resp_len:
        chunk = sock.recv(resp_len - len(asrep_raw))
        if not chunk:
            break
        asrep_raw += chunk
    sock.close()
    return asrep_raw  # байты для parse_asrep()

Формирование самого AS-REQ — отдельная задача: нужно собрать ASN.1-структуру с именем пользователя, realm, списком запрашиваемых etype и дополнительными полями. Полный код занимает 80–100 строк. Ключевой момент: чтобы DC вернул RC4-шифрованный ответ, в поле etype AS-REQ нужно указать значение 23. Если запросить только AES — DC зашифрует enc-part AES-ключом, и hashcat потребует другой режим.

Ожидаемый результат: если у целевой учётной записи отключена pre-authentication, функция вернёт валидный AS-REP. Если pre-authentication включена — DC ответит ошибкой KRB5KDC_ERR_PREAUTH_REQUIRED. Различить просто: в ошибке msg-type равен 30 (KRB-ERROR), в успешном ответе — 11 (AS-REP).

Kerberoasting vs AS-REP Roasting: когда что применять

Оба вектора атаки Active Directory используют офлайн-подбор Kerberos-тикетов, но нацелены на разные мишени.

Критерий AS-REP Roasting Kerberoasting
Цель Учётки без pre-authentication Сервисные учётки с SPN
Нужны credentials Нет (достаточно списка имён) Да (любая доменная учётка)
Что крекаем enc-part из AS-REP enc-part из TGS-REP
Hashcat mode 18200 (RC4) / 19600 (AES-128) / 19700 (AES-256) 13100 (RC4) / 19600 (AES-128) / 19700 (AES-256)
MITRE ATT&CK T1558.004 T1558.003
Встречаемость Реже (нужна ошибка конфигурации) Чаще (SPN-аккаунты повсеместны)

Ключевое отличие для практики: AS-REP Roasting работает без единой учётной записи в домене. Достаточно сетевого доступа к DC и файла с именами пользователей. Kerberoasting всегда требует хотя бы low-privilege доменный аккаунт.

На внутренних пентестах AS-REP Roasting попадается нередко, особенно в сетях с унаследованными учётными записями. По данным CrowdStrike Global Threat Report 2025, 75% вторжений используют валидные учётные данные. IBM X-Force фиксирует рост атак с использованием credentials на 71% за 2024 год. AS-REP Roasting — часть этого тренда: чем больше legacy-аккаунтов с отключённой pre-authentication болтается в домене, тем проще атакующему собрать первый пароль.

Как защитники обнаруживают AS-REP Roasting атаку

Зачем этот раздел пентестеру: понимание detection-логики помогает оценить шумность атаки и включить рекомендации по мониторингу в отчёт.

Три индикатора из практики blue team (по данным исследований HackTheBox и Picus Security):

Event ID 4768 — запрос TGT на контроллере домена. Генерируется тысячами в минуту в любой AD-среде. Но комбинация трёх условий резко сужает выборку: Pre-Authentication Type = 0 (отключена), Ticket Encryption Type = 0x17 (RC4-HMAC), Service Name = krbtgt. Совпадение всех трёх — высокая вероятность AS-REP Roasting.

Event ID 4738 — изменение учётной записи. Генерируется при любом изменении атрибутов учётки, поэтому требует фильтрации по изменению бита DONT_REQ_PREAUTH в атрибуте UserAccountControl. Без такой фильтрации индикатор даёт огромное количество false positives. Злоумышленник с правами на объект может временно снять флаг, получить хеш и вернуть настройку — весь цикл за минуту.

Event ID 5136 — изменение объекта Active Directory через LDAP. Дополнительный источник информации о модификации атрибута UserAccountControl, содержащего флаг DoesNotRequirePreAuth.

MITRE D3FEND (knowledge graph защитных техник) рекомендует пять контрмер против T1558.004: Credential Hardening (D3-CH), Multi-factor Authentication (D3-MFA), Credential Rotation (D3-CRO), Token Binding (D3-TB) и Token-based Authentication (D3-TBA).

На практике самая эффективная защита — убрать флаг DoesNotRequirePreAuth со всех учётных записей. Если legacy-приложение требует отключённой pre-authentication — изолировать аккаунт, поставить пароль длиной 25+ символов (не из словаря) и мониторить Event ID 4768 для этой конкретной учётки.

Точка зрения

Знать impacket-GetNPUsers полезно. Уметь написать собственный Python-скрипт для пентеста Active Directory — полезнее. Не потому что готовый инструмент плох, а потому что в реальных проектах регулярно возникают ситуации, которые готовый тулинг не покрывает: нестандартный DC, кастомный etype, задача вытащить из AS-REP метаданные, не предусмотренные автором.

Многие пентестеры, которых я вижу на HTB и на реальных проектах, останавливаются на уровне «запустил GetNPUsers, получил хеш, скормил hashcat». Это работает в большинстве случаев. Но в остальных — нестандартные конфигурации, DC с AES-only policy, закрытый исходящий трафик — требуют ручного разбора. Именно в этих 20% разница между оператором инструментов и пентестером, который решает задачу.

По данным IBM X-Force, за 2024 год атаки с использованием валидных учётных данных выросли на 71%, в dark web ежедневно появляются тысячи свежих пар логин-пароль. Пока в корпоративных AD живут legacy-аккаунты с отключённой pre-authentication и паролем из словаря, техника останется рабочей. Если хочешь отработать эту цепочку руками — на курсе WAPT разбирают AS-REP Roasting в модуле по атакам на Active Directory с лабами.

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