Кража билетов TGT Kerberos используя технику GIUDA

Кража билетов TGT Kerberos GIUDA

Существуют различные способы злоупотребления сеансом работы пользователя на устройстве. Но знаете ли вы, что можно подделать получение пользователем TGT-билета с помощью вполне легитимных функций Windows? Недавно было представлены несколько методов такого рода атак. Самыми интересными из них являются WTSImpersonator и GIUDA. Второй позволяет получить билеты вошедшего в систему пользователя, даже не имея его пароля! Сегодня разберемся, как работает GIUDA, и попробуем написать реализацию на языке C++.

Еще по теме: Атака Key List Kerberos

Что такое TGT

TGT представляет собой особый вид билета, который выдается пользователю при успешной аутентификации в системе Kerberos. Этот процесс осуществляется с использованием пароля пользователя и предотвращает передачу пароля по сети.

Вот как работает TGT.

  1. Аутентификация пользователя:
    • Пользователь вводит свой логин и пароль.
    • Сервер аутентификации Kerberos проверяет правильность пароля.
    • Если аутентификация проходит успешно, сервер генерирует TGT.
  2. Выдача TGT:
    • TGT содержит информацию о пользователе, времени его аутентификации и другие метаданные.
    • TGT также содержит сессионный ключ, который будет использоваться для дальнейшего взаимодействия.
  3. Использование TGT для получения других билетов:
    • Пользователь может использовать TGT для запроса билетов на доступ к различным ресурсам в сети.
    • Каждый билет содержит информацию о конкретном ресурсе и защищен сессионным ключом, что обеспечивает безопасность передачи.

Сеанс входа в систему

При авторизации пользователя в Windows создается сессия юзера, в которой хранятся все данные пользователя. Для всех новых пользователей создается новый сеанс. Например, если на машине одновременно находятся два пользователя, то сессий будет две.

Сеансы входа в систему выглядят
Сеансы входа в систему выглядят

Каждый сеанс имеет имя LUID (локально уникальный идентификатор). По названию понятно, что LUID уникален для каждого сеанса. Информация сохраняется в формате структуры.

Собственно LUID представляется в виде двух значений: ULONG и LONG. При этом обычно заполнено только поле LowPart, а HighPart имеет значение .

Такая структура применяется всеми функциями WinAPI, так или иначе связанными с пользовательскими сессиями.

При помощи функции GetTokenInformation() можно узнать пользовательский LUID. Для этого функции должен быть передан токен процесса, выполняющегося от имени текущего пользователя.

Теперь пора показать, как зап­рашива­ются билеты Kerberos самой LSA. Это поможет нам под­менить LUID и получить чужой тикет.

Как LSA запрашивает билеты Kerberos

Для зап­роса TGS-билета LSA получа­ет SPN (service principal name, иден­тифика­тор служ­бы) и переда­ет на KDC. Мы можем зап­рашивать билеты TGS сами. Для это­го есть фун­кция LsaCallAuthenticationPackage().

Здесь

  • LsaHandle — хендл, ука­зыва­ющий на служ­бу LSA, который мож­но получить с помощью LsaRegisterLogonProcess() или LsaConnectUntrusted();
  • AuthenticationPackage — номер AP, с которым сле­дует вза­имо­дей­ство­вать;
  • ProtocolSubmitBuffer — переда­ваемый буфер, мы будем отда­вать KERB_RETRIEVE_TKT_REQUEST;
  • SubmitBufferLength — раз­мер переда­ваемо­го буфера;
  • ProtocolReturnBuffer — ответ от AuthenticationPackage. Нам при­летит струк­тура KERB_RETRIEVE_TKT_RESPONSE;
  • ReturnBufferLength — раз­мер буфера с отве­том;
  • ProtocolStatus — зна­чение, которое будет содер­жать код ошиб­ки от AP.

Итак, как запол­нить KERB_RETRIEVE_TKT_REQUEST, что­бы получить билет TGS? Струк­тура выг­лядит вот так:

Здесь:

  • MessageType — то, что нам нуж­но получить от AP. Ука­зыва­ем KerbRetrieveEncodedTicketMessage;
  • LogonID — LUID сес­сии, от лица которой про­исхо­дит обра­щение к AP. Имен­но в этот момент и будет под­менен LUID. Проб­лема в том, что если мы под­клю­чились к LSA через LsaConnectUntrusted(), то у нас не получит­ся ука­зать здесь LUID чужой сес­сии — LSA выдаст ошиб­ку 0x5 ERROR_ACCESS_DENIED, но если мы под­клю­чим­ся через LsaRegisterLogonProcess(), то смо­жем переда­вать сюда любой желан­ный LUID. И таким обра­зом смо­жем зап­рашивать билеты из чужой сес­сии;
  • TargetName — здесь ука­зыва­ем SPN служ­бы, на которую нуж­но получить билет;
  • CacheOptions — опции, свя­зан­ные с кешем LSA. Кеш LSA — это некое хра­нили­ще, в котором лежат билеты. Здесь тоже есть некото­рые осо­бен­ности. Если мы сра­зу ука­жем KERB_RETRIEVE_TICKET_AS_KERB_CRED (зна­чение для получе­ния билета в фор­ме KRB_CRED, сра­зу с сес­сион­ным клю­чом; под­робнос­ти — в дру­гой моей статье), то есть шанс не получить билет. Проб­лема в том, что в кеше LSA может не быть билета для той служ­бы, на которую мы хотим схо­дить. И если мы сра­зу ука­зыва­ем KERB_RETRIEVE_TICKET_AS_KERB_CRED, то LSA может прос­то не вер­нуть никако­го билета, пос­коль­ку воз­вра­щать нечего. Поэто­му при­дет­ся дваж­ды выз­вать фун­кцию LsaCallAuthenticationPackage(). Пер­вый раз — со зна­чени­ем KERB_RETRIEVE_TICKET_DEFAULT, вто­рой — с KERB_RETRIEVE_TICKET_AS_KERB_CRED. …DEFAULT отве­чает за зап­рос билета. То есть про­сим LSA обра­тить­ся к KDC и получить билет;
  • EncryptionType — жела­емый тип шиф­рования для зап­рошен­ного билета. Ука­зыва­ем KERB_ETYPE_DEFAULT — нам не прин­ципи­ален тип шиф­рования;
  • CredentialsHandle — исполь­зует­ся для SSPI, в дан­ном слу­чае неваж­но.

Кража билетов TGT

Мы разоб­рались с тем, как работа­ет зап­рос билетов Kerberos на локаль­ной сис­теме. Пора перехо­дить к экс­плу­ата­ции! Пол­ный исходный код про­екта можешь пос­мотреть в репози­тории.

Сна­чала мы перечис­ляем все име­ющиеся сес­сии, для это­го я соз­дал фун­кцию LogonInfo(). Она при­нима­ет ука­затель на струк­туру LUID, которая будет про­ини­циали­зиро­вана нуж­ной сес­сией. Фак­тичес­ки — у какого поль­зовате­ля нуж­но ста­щить билет. Ну или не «ста­щить», а получить новый, абсо­лют­но све­жий и чис­тый билет для каж­дого поль­зовате­ля.

При­мер работы фун­кции
При­мер работы фун­кции

Сле­дующим шагом под­клю­чаем­ся к LSA с помощью LsaRegisterLogonProcess(), что­бы передать LUID чужой сес­сии. Для вызова этой фун­кции нуж­на при­виле­гия SeTcbPrivilege. Ей обла­дает толь­ко учет­ная запись сис­темы. При­виле­гию, конеч­но, мож­но наз­начить и руками через GPO или с помощью Privileger.

До­бав­ление при­виле­гии SeTcbPrivilege
До­бав­ление при­виле­гии SeTcbPrivilege

Чувс­тву­ешь, что это нес­коль­ко неудоб­но? Поэто­му я добавил в код прос­тей­ший алго­ритм для повыше­ния при­виле­гий до учет­ной записи сис­темы. Здесь все стан­дар­тно:

  • По­луча­ем при­виле­гии SeDebugPrivilege и SeImpersonatePrivilege.
  • По­луча­ем токен про­цес­са, запущен­ного от лица сис­темы. Я получаю токен с Winlogon.
  • При­меня­ем токен к нашей прог­рамме с помощью ImpersonateLoggedOnUser().

Код я выделил в отдель­ный файл getsystem.cpp.

Те­перь у нас есть при­виле­гия SeTcbPrivilege, так как ей обла­дает учет­ная запись сис­темы. Сле­дующим шагом с помощью LsaLookupAuthenticationPackage() получа­ем номер AP Kerberos.

На­конец у нас есть хендл, LUID и номер AP Kerberos. Пора ломать!

Для это­го я написал отдель­ную фун­кцию AskTgs(). Она при­нима­ет все эти дан­ные.

И вызыва­ем LSA:

Те­перь LSA обра­тит­ся к KDC и получит новый тикет. Если мы сра­зу же поп­робу­ем его извлечь, то он не будет валид­ным. Точ­нее, в нем не будет сес­сион­ного клю­ча и поль­зовать­ся им не получит­ся.

По­это­му, убе­див­шись, что вызов успешно совер­шен, меня­ем CacheOptions на  KERB_RETRIEVE_TICKET_AS_KERB_CRED и обра­щаем­ся к LSA.

Би­лет будет лежать в поле Ticket.EncodedTicket. Пред­лагаю пос­мотреть, как это работа­ет.

Пред­ста­вим, что во вре­мя пен­теста мы сло­мали машину, на которую ходит поль­зователь CRINGE\petka. Запус­каем TGSThief и видим его сес­сию.

Пе­реда­ем номер его сес­сии.

Ука­зание сес­сии
Ука­зание сес­сии

За­тем про­писы­ваем нуж­ный SPN, то есть служ­бу, на которую нуж­но получить TGS-билет. И получа­ем его.

Ус­пешный инжект билета
Ус­пешный инжект билета

Ус­пех! Теперь можем ходить от лица CRINGE\petka на dc01.cringe.lab.

TGT — это TGS

Ка­залось бы, получе­ние билета TGS — отличный резуль­тат! Но всег­да хочет­ся боль­шего, прав­да? Зна­ешь ли ты, что билет TGT — это фак­тичес­ки билет TGS, но на служ­бу krbtgt? Получа­ется, что у нас есть TGS-билет на krbtgt, а служ­ба krbtgt поз­воля­ет выписы­вать дру­гие TGS-билеты. Вот и всё.

К такому выводу я при­шел, ког­да писал дам­пер билетов. Доказать про­ще прос­того: вновь запус­каем TGSThief, толь­ко в этот раз ука­зыва­ем krbtgt/cringe.lab.

По­луче­ние TGT-билета
По­луче­ние TGT-билета

Бин­го! Мы можем зап­рашивать чужие билеты TGT! Таким обра­зом, если при пен­тесте уда­лось зах­ватить какой‑то хост, куда ходят поль­зовате­ли, то, исполь­зуя TGSThief, получит­ся раз­добыть и TGT этих поль­зовате­лей. При­чем билеты TGT будут абсо­лют­но све­жие, новые, толь­ко что зап­рошен­ные.

Заключение

Те­перь ты зна­ешь, как зло­упот­реблять сес­сиями поль­зовате­лей по‑новому. Эта ата­ка в оче­ред­ной раз под­твержда­ет, что на сис­темы нель­зя ходить от лица адми­нис­тра­тора домена. В про­тив­ном слу­чае ата­кующий смо­жет зах­ватить всю сеть в счи­таные минуты.

ПОЛЕЗНЫЕ ССЫЛКИ:

Дима (Kozhuh)

Эксперт в кибербезопасности. Работал в ведущих компаниях занимающихся аналитикой компьютерных угроз. Анонсы новых статей в Телеграме.

Добавить комментарий