Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье мы рассмотрим не очень популярную, но крайне интересную атаку, реализовав которую атакующий сможет получить TGT-билет пользователя, даже не зная его пароля либо хеша. Хакеру достаточно лишь выполнить код от лица этого пользователя, а все остальное сделает за нас KDC.
Active Directory предоставляет мощный набор функций для делегирования прав на олицетворение пользователей конкретной службе. Существует три вида делегирования: неограниченное, ограниченное и ограниченное на основе ресурсов. Про каждый уже рассказывалось много раз, но какие еще возможности таит в себе механизм делегирования?
При неограниченном делегировании администратор приходит к службе и говорит: «Теперь ты можешь олицетворять клиентов на других службах». Причем на абсолютно любых службах (отсюда и название — неограниченное). Как это работает?
Во‑первых, клиент обращается к службе с неограниченным делегированием. KDC видит, что эта служба имеет специальный флаг TRUSTED_FOR_DELEGATION
(он сигнализирует о том, что у службы настроено неограниченное делегирование), поэтому возвращает клиенту TGS на эту службу, но со специальным флагом OK-AS-DELEGATE
. Следующим шагом клиент проверяет этот самый флаг. Если он видит, что флаг установлен, то понимает: служба использует неограниченное делегирование, поэтому клиент вновь идет к KDC и запрашивает специальный FORWARDED TGT, который будет отправлен службе.
Внутри этого тикета будет лежать также сессионный ключ, что позволит службе без проблем олицетворять клиента. Далее у клиента будет TGS-тикет на службу, а также этот FORWARDED TGT, поэтому пора идти к службе. Генерируется запрос AP-REQ, который содержит этот самый FORWARDED TGT.
FORWARDED TGT в AP-REQ
Причем тикет будет находиться внутри так называемого аутентификатора. Он позволяет предотвратить возможность релей‑атаки на этап AP-REQ, так как аутентификатор зашифрован сессионным ключом, а также содержит (в случае обычного AP-REQ) имя принципала клиента и таймстемп. Если же служба настроена с неограниченным делегированием, то в запрос AP-REQ, который отправится службе, попадет не только таймстемп и имя принципала, но и FORWARDED TGT. Причем этот самый FORWARDED TGT будет лежать внутри аутентификатора. Сессионный ключ для шифрования аутентификатора клиент получает в ответе TGS-REP, который идет до AP-REQ.
Таким образом, у атакующего появляется возможность расшифровать аутентификатор из AP-REQ, а затем получить TGT-билет пользователя, который инициировал обращение к службе с неограниченным делегированием, ведь на руках у него будет зашифрованный блоб и ключ для его дешифровки.
Чтобы успешно получить TGT, нужно, чтобы выполнялись следующие требования:
Итоговый алгоритм достаточно простой:
Итак, сначала создаем файл Header.h
, в котором указываем все нужные заголовочные файлы, подгружаемые либы, а также прототип одной‑единственной функции.
#pragma once#define SECURITY_WIN32#include <windows.h>#include <sspi.h>#include <DsGetDC.h>#include <NTSecAPI.h>#include <iostream>#include <locale.h>#include <wincrypt.h>#include <WinBase.h>#define DEBUG#pragma comment (lib, "Secur32.lib")#pragma comment (lib, "NetApi32.lib")#pragma comment(lib,"Crypt32.lib")DWORD TgtDeleg(LPCWSTR);
Теперь стоит предусмотреть два варианта работы инструмента: в первом случае служба с неограниченным делегированием будет обнаружена автоматически (достаточно только имени домена), а во втором атакующий собственноручно сможет указать нужный SPN.
Получение сессионного ключа и AP-REQ через указание домена
Ручное указание SPN
Главная функция инструмента не очень большая. Сначала получаем список аргументов, причем проверяем: если их не три, то атакующий указал что‑то не то, поэтому вызываем функцию, которая расскажет, как использовать инструмент.
int wmain(char argc, wchar_t* argv[]) { setlocale(LC_ALL, ""); ShowAwesomeBanner(); if (argc != 3) { ShowUsage(); } ....}void ShowUsage() { std::wcout << L"tgtdeleg.exe 1 <DOMAIN NAME>ntEx: tgtdeleg.exe 1 cringe.lab" << std::endl; std::wcout << L"tgtdeleg.exe 2 <SPN With Unconstrained Deleg>ntEx: tgtdeleg.exe 2 CIFS/dc01.cringe.lab" << std::endl; exit(-1);}
Информация об использовании инструмента
Если же пользователь нигде не напортачил, то переходим к парсингу аргументов. В первом случае, когда указывается только имя домена, вызывается функция GetDomainController()
.
LPCWSTR targetname = NULL; switch (*argv[1]) { case '1': targetname = GetDomainController(argv[2]); break; ...
Эта функция позволяет получить DNS-имя контроллера домена. Мы берем контроллер домена потому, что на нем по умолчанию включено неограниченное делегирование. Получить имя можно с помощью функции DsGetDcName()
.
LPCWSTR GetDomainController(wchar_t* domainName) { PDOMAIN_CONTROLLER_INFO dcInfo = NULL; DWORD err = DsGetDcName(NULL, (LPCWSTR)domainName, NULL, NULL, DS_RETURN_DNS_NAME | DS_IP_REQUIRED, &dcInfo); if (err != ERROR_SUCCESS) { std::wcout << L"[-] Cant Get DC Name, try use 2 mode: " << err << std::endl; exit(-1); } return dcInfo->DomainControllerName;}
После получения имени убираем из него первые два символа слеша (так как функция вернула \dc01
, а нам нужно просто dc01
), а затем добавляем к полученному имени службу CIFS. В итоге у нас появляется валидный SPN на службу CIFS контроллера домена.
targetname = removeLeadingCharacters(targetname);#ifdef DEBUG std::wcout << L"[+] Target: " << targetname << std::endl;#endif LPCWSTR SPN = addCIFS(targetname);
Функция removeLeadingCharacters
просто чуть‑чуть смещает указатель на полученную строку, чтобы первые два символа \
как бы пропали.
LPCWSTR removeLeadingCharacters(LPCWSTR originalString) { LPCWSTR stringPtr = originalString; if (stringPtr[0] == L'' && stringPtr[1] == L'') { stringPtr += 2; } return stringPtr;}
А функция addCIFS()
добавляет строку CIFS/
к имени компьютера.
LPCWSTR addCIFS(LPCWSTR originalString) { size_t originalSize = wcslen(originalString); size_t cifsSize = 5; size_t newSize = originalSize + cifsSize + 1; LPWSTR newString = new WCHAR[newSize]; wcscpy_s(newString, newSize, L"CIFS/"); wcscat_s(newString, newSize, originalString); return newString;}
Есть и второй вариант — пользователь должен самостоятельно указать SPN. Здесь никакого парсинга тогда не потребуется. Сразу передаем полученный SPN в функцию TgtDeleg()
, в которой реализована логика получения сессионного ключа и блоба AP-REQ.
case '2': if (TgtDeleg(argv[2]) == 0) { std::wcout << L"[+] TgtDeleg Success" << std::endl; return 0; } else { std::wcout << L"[-] TgtDeleg Error" << std::endl; return -1; } break; default: std::wcout << L"[-] No such mode" << std::endl; ShowUsage(); return 0; }
Переходим в сердце программы — в функцию TgtDeleg()
. Она принимает один‑единственный аргумент — это SPN целевой службы. Затем начинается, как кто‑то очень интересно выразился, «магия SSPI». В действительности никакой магии нет. SSPI можно считать эдакой апишкой, через которую разработчики могут связываться с поставщиками безопасности (Security Packages). Возможности SSPI очень большие: шифрование, подпись, выстраивание контекста. Именно функции SSPI позволят нам сымитировать обращение к службе с неограниченным делегированием.
Источник: xakep.ru