В 2023 году мы запустили блог центра исследования киберугроз Solar 4RAYS, где делимся аналитикой об актуальных угрозах, результатами расследований инцидентов, полезными инструментами для реагирования на кибератаки и другими практическими материалами. Наиболее интересные исследования мы публикуем и здесь, и это — одно из них.
В конце прошлого года мы в команде Solar 4RAYS провели расследование атаки на российскую телеком‑компанию. Её сеть была скомпрометирована азиатской APT‑группировкой, которую мы назвали Obstinate Mogwai (или «Упрямый Демон» в переводе с английского). Почему «Упрямый?» Хакеры вновь и вновь проникали в атакованную организацию с помощью давно известной уязвимости десериализации ненадежных данных в параметре VIEWSTATE среды ASP.NET (далее будем называть это десериализацией VIEWSTATE).
Мы смогли окончательно закрыть злоумышленникам все возможности для проникновения. Сегодня мы расскажем историю исследования уязвимости, опишем, как обнаружить подобные атаки и как на них реагировать, а в конце приведем индикаторы компрометации.
Что такое VIEWSTATE
VIEWSTATE — это параметр в ASP.NET Framework, в котором сохраняется состояние ASPX‑страницы при выполнении HTTP‑запросов (например, значения элементов управления в форме и другая информация о странице). HTTP является протоколом без сохранения состояния (stateless), поэтому в ASP.NET эту задачу решают с помощью VIEWSTATE. В HTTP‑запросах данные VIEWSTATE сериализуются и десериализуются на стороне получателя.
В ASP.NET для этого используется класс ObjectStateFormatter, который сам Microsoft назвал ненадежным и не рекомендовал в качестве провайдера сериализации объектов. Он же является одной из причин существования RCE‑уязвимости.
Исследования уязвимости десериализации и атак, с ней связанных, — это отдельная большая тема, о которой выпущено большое количество публикаций и статей. Для оценки масштаба вспомним самые важные события, связанные с десериализацией в ASP.NET.
Сентябрь 2014 — Microsoft выпускает патч KB 290 524, который по умолчанию включает MAC‑валидацию для всех версий ASP.NET (с 1.1 до 4.5.2).
Ноябрь 2014 — эксперт Александр Херцог (Alexandre Herzog) на конференции Application Security Forum — Western Switzerland (@appsec_forum) демонстрирует уязвимость десериализации VIEWSTATE в SharePoint. Представленный способ опирался на отсутствие MAC‑валидации в ASP.NET.
Июль 2017 — эксперты Альваро Муноз (Alvaro Muñoz) и Олександр Мирош (Oleksandr Mirosh) выступают с докладом «Friday the 13th JSON Attacks» на Black Hat 2017 в США, где показывают гаджеты для эксплуатации десериализации VIEWSTATE.
Апрель 2019 — известный эксперт в области десериализации Соруш Далили выпускает своё исследование проблемы. Приводим его краткую выжимку: выпущенный Microsoft в 2014 году патч затрудняет использование уязвимости, но не закрывает её полностью: проверку MAC‑валидации можно успешно проходить, если у злоумышленника есть доступ к веб‑серверу, а именно — к ключам валидации VIEWSTATE (подробнее о них — далее в статье).
Исследование номинировали на Pwnie Awards как «Most under‑hyped research» (самое недостаточно раскрученное исследование). Среди прочего в тексте исследования упоминается, что уязвимость относится к WON'T FIX (не будет исправляться). Одно предложение из описания заявки на номинацию красноречиво описывает суть уязвимости:
Другими словами, если злоумышленники каким‑либо образом получают доступ к ключам валидации VIEWSTATE веб‑приложения на сервере, то они могут, используя уязвимость десериализации, удаленно выполнять код на этом сервере.
Февраль 2020 — Microsoft выпускает патч для CVE-2020–0688 — уязвимости, смысл которой в том, что у всех версий Exchange после установки были одинаковые ключи валидации для backend ECP. Вскоре стали доступны публичные POC. В данном случае для успешной эксплуатации требовались валидные учетные данные, которые позволяли получить доступ до backend панели ECP. Эту уязвимость можно рассматривать как частный случай уязвимости десериализации VIEWSTATE, так как CVE-2020–0688 работала только на один путь — backend ECP. Тем не менее, эта важная уязвимость, которая могла напомнить злоумышленникам о десериализации и её возможностях, так как именно после этой CVE стали появляться публичные отчеты об эксплуатации уязвимости десериализации VIEWSTATE в общем формате (на произвольных путях) в дикой среде.
Атаки с помощью десериализации в дикой природе
Июнь 2020 — Австралийский центр кибербезопасности (ACSC) Управления радиотехнической обороны выпустил рекомендации 2020–008 об атаке Copy‑paste compromises, в которых были описаны тактики и техники неизвестной прогосударственной группировки, которая эксплуатировала уязвимости десериализации:
Telerik UI CVE-2019–18 935 — уязвимость десериализации в.NET JavaScriptSerializer. Использовали для запуска реверс шелла;
CVE-2020–0688 — злоумышленники использовали уязвимость для размещения различных веб‑шеллов (кастомных и open source), в том числе фреймворка js_eval собственной разработки, который работает только в памяти и является многомодульным;
Вредоносные dotm‑шаблоны Word, которые также загружали Powershell Empire с помощью десериализации нагрузок «вручную» — через вызов десериализации уязвимым провайдером — BinaryFormatter.
В мае 2020 года были выпущены рекомендации:
2020–004 по обнаружению эксплуатации уязвимости в CVE-2019–18 935 в ПО Telerik, а также по реагированию на такую атаку.
2020–006 по обнаружению эксплуатации десериализации VIEWSTATE и уязвимости в ПО Telerik, а также по реагированию на подобные атаки. В этих рекомендациях впервые упоминалось, что для обнаружения атак десериализации можно использовать событие Application Event ID 1316 (подробнее о нём — далее в тексте).
Июль 2021 — эксперты SYGNIA выпустили отчет по атаке группировки Praying Mantis, которая использовала 4 разные уязвимости десериализации:
Checkbox Survey RCE Exploit (CVE-2021–27 852) — десериализация кастомной реализации VIEWSTATE для reflective загрузки вредоноса NodeIISWeb — очень похож на фреймворк js_eval из рекомендаций Австралийского центра кибербезопасности;
Десериализация VIEWSTATE с помощью украденных ключей IIS — злоумышленники несколько раз возвращались в инфраструктуру этим методом, им же они пользовались для горизонтального перемещения;
Десериализация объекта из ASP.NET MSSQL базы — злоумышленники записывали в MSSQL базу вредоносный объект в сериализованном виде, который далее запрашивал и десериализовывал IIS‑сервер когда получал запрос со специально сформированным cookies. В этом случае также загружали вредонос NodeIISWeb;
Telerik UI CVE-2019–18 935 — уязвимость десериализации в.NET JavaScriptSerializer. Использовали для загрузки веб‑шеллов.
Высока вероятность, что эксперты SYGNIA и ACSC столкнулись с одной и той же группировкой, так как было много пересечений по тактикам, техникам и процедурам.
Март 2022 — эксперты Mandiant поделились большим набором кейсов атак группировки APT41, в большинстве из которых эксплуатировались уязвимости десериализации VIEWSTATE:
Июнь 2020 — APT41, через уязвимость directory traversal, получили доступ к web.config файлу с ключами проприетарного веб‑приложения на ASP.NET. Ключи использовали для десериализации вредоносных VIEWSTATE‑нагрузок и загрузки сборок в память. Сборки записывали веб‑шеллы по заранее закодированным путям на файловой системе. Также использовался гаджет DisableActivitySurrogateSelectorTypeCheck утилиты ysoserial.net.
Май 2021 — как и в июне 2020 злоумышленники эксплуатировали десериализацию VIEWSTATE в проприетарном веб‑приложении. Откуда были получены ключи неизвестно (вероятно, также через directory traversal уязвимость).
Июнь‑декабрь 2021 — использование уязвимости нулевого дня в ASP.NET веб‑приложении USAHERDS (диагностическая система оповещения о чрезвычайных ситуациях в области охраны здоровья животных) для атаки на правительства нескольких штатов в США. Уязвимость аналогична CVE-2020–0688 (использование статических ключей). Предполагается, что первоначальный доступ к ключам был получен также через directory traversal уязвимость.
Март 2023 — Агентство по кибербезопасности и защите инфраструктуры (CISA), ФБР и MS‑ISAC выпустили совместные рекомендации по противодействию атакам с помощью уязвимостей десериализации. Рекомендации создали на основе анализа атак нескольких группировок, произошедших в ноябре 2022 на множество правительственных IIS‑серверов. В атаках использовалась уязвимость десериализации в ПО Progress Telerik.
Ноябрь 2023 — эксперты Solar 4RAYS реагируют на инцидент в инфраструктуре телеком‑компании, где группировка Obstinate Mogwai эксплуатирует уязвимости десериализации VIEWSTATE для повторного возвращения.
Эксплуатация десериализации VIEWSTATE в исполнении Obstinate Mogwai
В конце 2023 года в процессе реагирования на инцидент мы стали замечать на Exchange‑серверах выполнение powershell‑команд от процесса w3wp — worker‑процесса Exchange. Причем команды выполнялись от самого процесса w3wp без создания дочерних процессов powershell.exe.
Примеры выполняемых команд:
net user <REDACTED>
gci c:\programdata\
reg query HKEY_LOCAL_MACHINE\system\currentcontrolset\control\securityproviders\wdigest /v uselogoncredential
Get-ItemProperty -Path HKLM:\system\currentcontrolset\control\securityproviders\wdigest).uselogoncredential
Get-ChildItem -Path "c:\programdata"
Get-ChildItem -Path "c:\users"
Remove-Item -Path "c:\programdata\1.log"
Remove-Item -Path "c:\windows\system\l.txt"
Test-Connection -Count 1 -ComputerName <REDACTED>
Resolve-DnsName -Name <REDACTED>
Remove-Item -Path "C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth\Current\themes\resources\frown.aspx"
Remove-Item -Path "c:\windows\temp\user.log"
С помощью powershell-команд на Exchange-сервер копировались и другие инструменты атакующих (например, powershell-скрипт Invoke-SMBClient из фреймворка Inveigh), а также выполнялась эксфильтрация данных и содержимого почтовых ящиков.
К тому времени используемые в прошлых атаках вредоносные aspx-файлы (вебшеллы) из доступных извне каталогов Exchange мы уже удалили, но атакующие продолжали выполнять команды. Мы стали искать, каким образом и с помощью чего они это делают.
В IIS-логах в окрестности времени выполнения команд были обнаружены следующие POST-запросы с характерным UserAgent:
POST /owa/auth/RedirSuiteServiceProxy.aspx 443 77.223.109[.]164 python-requests/2.30.0
POST /owa/auth/RedirSuiteServiceProxy.aspx 443 45.12.67[.]18 python-requests/2.30.0
POST /owa/auth/RedirSuiteServiceProxy.aspx 443 77.223.109[.]163 python-requests/2.30.0
Причем все они выполнялись к легитимному aspx-файлу (C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth\RedirSuiteServiceProxy.aspx).
В ходе более тщательного поиска индикаторов по всему образу одного из Exchange-серверов в журнале Windows мы обнаружили событие с ID 1316:
В параметре PersistedState в событии 1316 отображается base64-закодированный сериализованный VIEWSTATE, который при десериализации (во время или после) как раз и вызвал Exception, из-за которого это событие и появилось в журнале событий. После декодирования из basе64 VIEWSTATE выглядел следующим образом:
Первые два байта представляют собой заголовок сериализованного VIEWSTATE – FF 01, вот его общая структура:
struct SERIALIZED_VIEWSTATE {
byte Marker_Format = 0xFF; // https://referencesource.microsoft.com/#System.Web/UI/ObjectStateFormatter.cs,116
byte Marker_Version_1 = 0x01; // https://referencesource.microsoft.com/#System.Web/UI/ObjectStateFormatter.cs,117
byte token = 0x32; // https://referencesource.microsoft.com/#System.Web/UI/ObjectStateFormatter.cs,90
variable_size length_7BitEncodedInt; // size may vary: [1-4] bytes, https://referencesource.microsoft.com/#mscorlib/system/io/binaryreader.cs,582
SERIALIZED_DATA d; // has size of length_7BitEncodedInt
_HashSize MAC_bytes; //_HashSize depends on validation algorithm (16, 20 or 32 bytes), https://referencesource.microsoft.com/#System.Web/Configuration/MachineKeySection.cs,88
}
struct SERIALIZED_DATA {
struct SerializationHeaderRecord { // https://referencesource.microsoft.com/#mscorlib/system/runtime/serialization/formatters/binary/binarycommonclasses.cs,455
byte binaryHeaderEnum = 0x0 (SerializedStreamHeader); // https://referencesource.microsoft.com/#mscorlib/system/runtime/serialization/formatters/binary/binaryenums.cs,27
dword topId = 0x1;
dword headerId = 0xFFFFFFFF;
dword majorVersion = 0x1; // https://referencesource.microsoft.com/#mscorlib/system/runtime/serialization/formatters/binary/binarycommonclasses.cs,417
dword minorVersion = 0x0; //https://referencesource.microsoft.com/#mscorlib/system/runtime/serialization/formatters/binary/binarycommonclasses.cs,418
}
data; // serialized data itself
}
Приведенных выше структур в исходном коде нет — это наша интерпретация для лучшего понимания. По ссылкам можно найти исходный код и ознакомиться с подробностями реализации.
В сериализованных данных наше внимание привлек гаджет (сериализованный код для выполнения заданных команд) ActivitySurrogateDisableTypeCheck, а именно строчка «xaml_payload», по которой мы вышли на утилиту ysoserial.net. Этот инструмент с открытым исходным кодом позволяет генерировать нагрузки для эксплуатации различных уязвимостей десериализации. Его предназначение — отключение проверок, введенных в.NET 4.8+ для другого гаджета ActivitySurrogateSelector. Он, в свою очередь, позволяет загружать произвольные.NET сборки (далее — сборки) в память. Судя по структуре наблюдаемых нами VIEWSTATE‑нагрузок, злоумышленники генерировали их с помощью ysoserial.net, и все они были гаджетами ActivitySurrogateDisableTypeCheck.
Больше никаких VIEWSTATE‑нагрузок мы не нашли. Следов применения гаджета ActivitySurrogateSelector также не обнаружили. Однако спустя некоторое время мы все‑таки поймали настоящий payload — благодаря ошибке самих злоумышленников. А как именно они ошиблись, расскажем далее.
Злодеи тоже ошибаются
У гаджета ActivitySurrogateDisableTypeCheck есть важная особенность. Он отключает проверки .NET 4.8+ на время жизни соответствующего w3wp-процесса. Как известно, w3wp-процессы могут перезапускаться:
по расписанию;
при зависании (например, если процесс не ответит на heart beat запрос IIS);
при ручном перезапуске;
по другим причинам.
Но если бы они не перезапускались, то можно было бы использовать этот гаджет один раз и больше не применять до тех пор, пока «жив» атакуемый w3wp‑процесс.
Расписание перезапуска отображается в расширенных настройках пулов приложений в IIS Manager в разделе Recycling:
У заказчика время перезапуска worker-процесса, который отвечал за приложение OWA, составляло 14 дней, а не установленные по умолчанию 29 часов. В результате мы наблюдали следующий timeline событий:
11:00 Event ID 1316 Viewstate was invalid – отправка гаджета ActivitySurrogateDisableTypeCheck XX:YY перезапуск w3wp процесса и потеря эффекта гаджета ActivitySurrogateDisableTypeCheck 16:00 Event ID 1316 Viewstate was invalid – неудачная попытка выполнения powershell-команды из-за перезапущенного w3wp процесса и отсутствующего эффекта гаджета ActivitySurrogateDisableTypeCheck. |
Именно в этом событии в PersistedState находилась VIEWSTATE-нагрузка, которая позволяла атакующим выполнять powershell-команды. То есть, судя по всему, злоумышленники допустили ошибку и забыли “активировать” гаджет ActivitySurrogateDisableTypeCheck, так как, вероятно, думали, что сохранился эффект от прошлого гаджета, использованного в 11:00.
После декодирования из basе64 VIEWSTATE выглядел следующим образом:
Это был гаджет ActivitySurrogateSelector. Он загружал в память сборку в виде dll-файла с помощью Assembly.Load(byte[]) и запускал её вызовом конструктора сборки. В результате анализа выяснилось, что dll позволяла выполнять powershell-команды (подробности далее).
Так мы, наконец, поняли, каким образом и с помощью какой уязвимости злоумышленники вновь и вновь восстанавливали присутствие в сети заказчика. Мы распознали десериализацию VIEWSTATE, для успешной эксплуатации которой необходимо знать как минимум ключ валидации VIEWSTATE. В тот момент ключи валидации пула OWA у заказчика были установлены в режиме автоматической генерации.
Для замены ключей мы пробовали перезапускать пулы и даже перезагружать почтовые серверы, но это не принесло желаемого результата. Во многих публичных статьях, которые связаны с десериализацией VIEWSTATE, приводят расплывчатые рекомендации на этот случай:
“Recommendation: Create a new Application Pool for your ASP.NET application when you need to reset all the credentials for any reasons. This will ensure that a new ASP.NET key will be generated for the application.” |
То есть для «сброса» ключей рекомендуется создать пул заново. В случае с Exchange‑сервером сделать это не так уж просто. Сложнее, наверное, только полная переустановка Exchange. Впрочем, как оказалось, даже переустановка Exchange не поможет изменить auto generated‑ключи, так как мастер‑ключ не удаляется из реестра, поэтому при повторной установке будут использованы те же самые ключи.
“In addition to these security measures, encryption and validation keys should be handled with care as sensitive credentials. If possible, use auto‑generated keys, otherwise routinely rotate the machine keys on your IIS servers to make sure you would not be susceptible to attacks where keys were stolen or leaked.” |
Рекомендуется по возможности использовать автоматически сгенерированные ключи, но нет никаких рекомендаций, что делать в случае их компрометации.
“Generating unique machineKey values is critical to the security of an ASP.NET web application because the values are used to secure the integrity of the ViewState.” |
Конкретных рекомендаций нет. В статье обращают внимание на то, чтобы значения ключей были уникальными. В случае автоматически сгенерированных ключей они будут уникальными, но снова нет информации, что делать при компрометации этих уникальных ключей.
Конкретные меры были описаны только в рекомендациях 2020-006 от ACSC в июне 2020, но там не раскрывалось, как менять автоматически сгенерированные ключи, которые по умолчанию используются в Exchange-серверах.
Несколько слов об атрибуции
Отметим несколько фактов касательно атрибуции расследованных нами инцидентов.
При реагировании на атаку группировки Obstinate Mogwai мы наблюдали следующую технику загрузки в память .NET бэкдора:
powershell exec bypass -f c:\programdata\microsoft\WDF\ctfmon.ps1 |
Загружаемый в память бэкдор имел некоторое сходство с C++ версией бэкдора KingOfHearts группировки IAmTheKing, про которую писали в 2020 г.
В результате выполнения Powershell команды расшифровывался файл C:\ProgramData\Microsoft\WDF\WDF.log, и в память powershell.exe загружалась .NET сборка, представляющая собой бэкдор. ctfmon.ps1 при этом весил 13MB, так как был обфусцирован следующим образом:
Именно такую уникальную обфускацию мы уже наблюдали в инцидентах 2020-2021 годов, про которые рассказывали в 2021 году в докладе «Operation LongChain. История одного расследования».
По данным Mandiant, APT41 в июне 2020 года использовала при эксплуатации десериализации VIEWSTATE гаджет ActivitySurrogateDisableTypeCheck, который мы также наблюдали при реагировании в нашем инциденте. Именно этот гаджет подвел злоумышленников и помог раскрыть одну из сборок фреймворка.
В исследованном инциденте Obstinate Mogwai для проведения атак десериализации использовали свой собственный фреймворк, который отличался от js_eval, описанного в кейсе ACSC в 2020 году.
Другими словами, надёжная атрибуция атак, которые мы расследовали, к какой‑либо из известных APT‑группировок — дело непростое, так как некоторые азиатские группы регулярно заимствуют друг у друга вредоносный инструментарий. В будущем мы посвятим этому вопросу отдельную публикацию.
VIEWSTATE exploitation framework
VIEWSTATE exploitation framework — наше имя для набора скриптов/библиотек, которыми оперировали злоумышленники в описываемой атаке. На основании проделанного исследования мы пришли к выводу, что оператор при проведении подобных атак работает на более высоком уровне — у него имеется нечто вроде оболочки, куда он вводит команды и получает результат в удобном формате.
На это указывает одна из особенностей работы фреймворка, а именно использование под каждое конкретное действие отдельной скомпилированной на ходу сборки. Нам удалось достать из памяти атакованного worker‑процесса следующие файлы:
Assembly name |
Compilation timestamp |
Main class name |
POST query param name |
Number of extracted assemblies |
Action |
Microsoft.Exchange.Management.Powershell.Support.dll |
2023-01-04 09:11:36 |
Ps |
cadata |
8 |
Выполнить powershell команду, зашифрованную в параметре cadata POST-запроса и отправить вывод в ответе |
Microsoft.Exchange.MessageSecurity.dll |
FileDown |
cadataIV |
2 |
Выполнить эксфильтрацию файла по пути, указанном в параметре cadataIV |
|
Microsoft.Exchange.UM.UMCommon.dll |
2023-01-04 09:11:35 |
FileView |
cadataSig |
1 |
Отобразить метаданные файла или каталога, указанных в параметре cadataSig |
Даты компиляции разных сборок практически совпадают. Конечно, в нашем распоряжении довольно небольшая выборка, но, скорее всего, она свидетельствует о том, что все доступные сборки компилировались каким‑либо скриптом для дальнейшего использования во фреймворке. В утилите ysoserial.net «из коробки» гаджет ActivitySurrogateSelectorFromFile требует на вход cs‑файл и компилирует его при каждом запуске. Но ничто не мешает изменить это поведение: заранее скомпилировать все используемые сборки и генерировать разные VIEWSTATE нагрузки в зависимости от ключей валидации. Также отметим, что у всех сборок отсутствует TypeLib ID. Либо он был удалён специально, либо сборки компилировались в режиме командной строки (как, например, в ysoserial.net).
Каждая сборка получала данные в одном параметре POST‑запроса в XOR‑зашифрованном и в base64-закодированном виде. Отдельно отметим, что названия параметров не случайны. Именно такие параметры используются в cookies в механизме FBA (Form‑based Authentication — специальный модуль IIS, который отвечает за конвертацию пары имени и пароля пользователя в cookies и обратно):
Этих cookies в памяти атакованного worker-процесса Exchange (пул OWA) очень много, что значительно усложняет восстановление POST-запросов злоумышленников. Нам не удалось получить ни один запрос, так как, скорее всего, garbage collector их удалил из-за большой активности самого worker-процесса.
После выполнения основного действия сборки (Ps, FileView и FileDown) отправляли зашифрованный с помощью XOR ответ злоумышленникам. Ключ шифрования во всех сборках использовался один и тот же:
45 AF 33 27 56 DF CA BB 12 67 9A 52 63 B8 2B C7 62 1F 10 8D 15 |
В таблице выше мы привели столбец Number of extracted assemblies, чтобы подчеркнуть вышеупомянутую особенность работы VIEWSTATE Exploitation Framework — одно действие — одна сборка.
Это означает, например, что для выполнения одной команды powershell будет отправлен отдельный POST‑запрос с Ps‑сборкой. Если злоумышленник снова захочет выполнить powershell‑команду, то будет отправлен тот же POST‑запрос, содержащий VIEWSTATE с Ps‑сборкой, но с другим параметром cadata. В памяти атакованного worker‑процесса это будет выглядеть так:
Видно, что в адресное пространство атакованного w3wp-процесса загружены одни и те же сборки Microsoft.Exchange.Management.Powershell.Support.dll, которые выполняют powershell-команду.
Рассмотрим сборки, используемые Obstinate Mogwai, которые нам удалось извлечь.
Ps-сборка
Сборка предназначена для выполнения зашифрованной с помощью XOR Powershell-команды, которая передается в POST-запросе в параметре cadata. Отметим, что дочерний процесс powershell.exe при этом не создается из-за использования класса PowerShell из пространства имён System.Management.Automation.
FileDown-сборка
Сборка предназначена для эксфильтрации файла, путь до которого указывается в XOR-зашифрованном и base64-закодированном виде в параметре cadataIV.
FileView-сборка
Сборка предназначена для получения метаданных указанного файла или каталога, путь до которого указывается в XOR-зашифрованном и base64-закодированном виде в параметре cadataSig.
Примечательно, что выходные данные сборки формируются в бинарном виде. В качестве наглядного примера этой логики можно рассмотреть следующий каталог и то, как будет выглядеть собранная о нем информация до XOR-шифрования:
Значение в attribs складывается из стандартных констант атрибутов файла или каталога по именам, но злоумышленники изменили их стандартные значения. По-видимому, это было сделано для того, чтобы атрибуты умещались в один байт.
Константа |
Значение в сборке |
Значение Windows |
FILE_ATTRIBUTE_REPARSE_POINT |
0x01 |
0x400 |
FILE_ATTRIBUTE_SYSTEM |
0x02 |
0x004 |
FILE_ATTRIBUTE_HIDDEN |
0x04 |
0x002 |
FILE_ATTRIBUTE_READONLY |
0x08 |
0x001 |
FILE_ATTRIBUTE_ARCHIVE |
0x10 |
0x020 |
FILE_ATTRIBUTE_DIRECTORY |
0x20 |
0x010 |
Наличие такой бинарной структуры для метаданных файлов и каталогов еще раз указывает на то, что злоумышленники, очевидно, обладают фреймворком, который в том числе преобразует эти данные в человекочитаемый вид.
Важно отметить, что в отличие от того же js_eval, в рамках существующей архитектуры фреймворка мы могли наблюдать только те сборки, которые были непосредственно использованы в атаке. Учитывая опыт исследования js_eval, где механизм выполнения модулей разворачивался на стороне жертвы, можно предположить, что существуют другие сборки, используемые в рамках нового фреймворка, такие как FileUpload (по аналогии с FileDown), загрузка shellcode и тд. Это делает VIEWSTATE Exploitation Framework серьезным инструментом с широким модульным функционалом, который можно легко расширять.
Обнаружение десериализации VIEWSTATE
В этом разделе мы делимся практическими советами по детектированию атак десериализации VIEWSTATE. В зависимости от конкретного случая, для обнаружения могут быть использованы разные техники, описанные ниже.
Обнаружение Memory-only сборок в памяти
Про обнаружение описанных выше .NET-сборок в памяти была написана не одна статья. Регион памяти процесса w3wp, в который была загружена Memory-only сборка с помощью метода Assembly.Load(byte[]), имеет отличительные параметры по которым его можно задетектить:
Тип MEM_MAPPED, хотя отсутствует объект отображенного в память файла (пустой столбец Use);
Параметр защиты области памяти PAGE_READWRITE, но без EXECUTE.
В упомянутой статье от Elastic Security Labs в том числе предлагается powershell-скрипт для поиска и дампа подобных сборок.
Обнаружение по событию Application Event ID 1316
Другой способ детектирования основывается на анализе журнала Windows Application, а именно, на двух разных типах одного события ID 1316:
Viewstate was invalid (Event detail code 50204).
The viewstate supplied failed integrity check (Event detail code 50203).
Данные события возникают, если произошло какое-либо исключение при десериализации VIEWSTATE.
Для понимания механизма возникновения этих исключений, нужно проследить цепочку вызовов в исходном коде, ведущей к десериализации VIEWSTATE. Она начинается в методе HiddenFieldPageStatePersister::Load, далее вызывается Util::DeserializeWithAssert, после чего выполнение переходит в метод ObjectStateFormatter::Deserialize, где как раз и происходит MAC-валидация (The viewstate supplied failed integrity check), а после – десериализация VIEWSTATE. Эта последовательность лучше всего представлена на стеке вызовов:
Примечательно, что в методе LoadPageStateFromPersistenceMedium присутствует вызов метода ShouldSuppressMacValidationException, который по умолчанию скрывает (не записывает в журнал событий Windows) событие ID 1316 The viewstate supplied failed integrity check (Event detail code 50203). При параметрах по умолчанию оно может появиться в журнале только в том случае, если в POST-запросе имеется корректный параметр __VIEWSTATEGENERATOR. Возможно, это было сделано разработчиками Microsoft в том числе для того, чтобы злоумышленник не получал дополнительной информации о возникшей ошибке валидации VIEWSTATE.
Теперь, когда мы знаем, где выбрасываются эти исключения, подробнее рассмотрим указанные в начале этого раздела два типа события ID 1316.
Событие The viewstate supplied failed integrity check (Event detail code 50203) связано с ошибкой валидации MAC, которая выполняется до десериализации, поэтому если мы видим в журнале это событие, то:
VIEWSTATE не был десериализован, то есть пока сервер “в безопасности”;
мы видим это событие в журнале, так как в POST-запросе использовался корректный параметр __VIEWSTATEGENERATOR.
Событие Viewstate was invalid (Event detail code 50204) возникает во время или после десериализации. Это означает, что у отправителя VIEWSTATE имеются корректные ключи валидации и необходимо уделить особое внимание содержимому VIEWSTATE (значение PersistedState из события).
Ранее мы уже приводили структуру заголовка сериализованного VIEWSTATE. Его первые два байта (Marker_Format и Marker_Version_1) во всех случаях одинаковые, так как представляют собой заголовок VIEWSTATE. В третьем байте указывается тип сериализованных данных. Мы проанализировали гаджеты утилиты ysoserial.net, которые подходят в качестве нагрузок для VIEWSTATE, и заметили, что все они «под капотом» представляют собой данные, сериализованные BinaryFormatter, то есть имеют тип Token_BinarySerialized (0×32). Эти данные можно использовать для детектирующего правила. В итоге первые три байта вредоносного VIEWSTATE в hex выглядят так:
FF 01 32 или “/wEy” в base64.
Таким образом, если в событии ID 1316 Viewstate was invalid (Event detail code 50 204) в первых четырех символах поля PersistedState мы видим /wEy, то обязательно стоит обратить на это внимание. Если подтвердится, что отправитель (поля User host address или Client IP) не является легитимным, то стоит «бить тревогу», так как у него есть возможность RCE на данном IIS‑сервере.
Также отметим другие полезные поля событий ID 1316:
Process ID — можно сразу знать, из какого PID дампить вредоносные сборки;
User‑Agent — если является уникальным, то можно отследить другие запросы. Обычно используется в паре с полями Client IP и User host address;
Account name — позволяет оценить права, с которыми может происходить RCE в случае вредоносного VIEWSTATE;
Machine name — имя атакованного хоста.
Особые случаи
Первая особенность, которая существенно усложняет детектирование атаки, – это использование зашифрованного VIEWSTATE, что исключает возможность оперативно анализировать его содержимое.
В версиях .NET 4.5+ он шифруется по умолчанию, даже если параметр конфигурации viewStateEncryptionMode установлен в Never.
В legacy версии <4.5 VIEWSTATE не шифруется по умолчанию, даже при параметре конфигурации viewStateEncryptionMode установленном в Always. Зашифрован VIEWSTATE или нет, определяется по наличию параметра __VIEWSTATEENCRYPTED в запросе.
Отметим, что использование legacy версии .NET (<4.5) или новой (>=4.5) зависит от параметра конфигурации MachineKeyCompatibilityMode и, например, не задается через IIS Manager:
<machineKey validationKey="<REDACTED>" decryptionKey="<REDACTED>" validation="SHA1" decryption="AES" compatibilityMode="Framework45" />
Параметр имеет следующие значения:
public enum MachineKeyCompatibilityMode {
// 2.0 SP1 mode encryption doesn't use IVs when encrypting data and is included only for legacy reasons.
Framework20SP1 = 0,
// 2.0 SP2 mode encryption uses IVs when encrypting data.
// See: DevDiv Bugs #137864 (http://bugcheck/bugs/DevDivBugs/137864)
Framework20SP2 = 1,
// 4.5 mode encryption uses IVs, signing, and a purpose (subkey derivation) to encrypt data.
// See: DevDiv #48244 (http://vstfdevdiv:8080/DevDiv2/web/wi.aspx?id=48244), which the overall Crypto DCR is a reaction to
// See: DevDiv #87406 (http://vstfdevdiv:8080/DevDiv2/web/wi.aspx?id=87406)
Framework45 = 2,
}
По умолчанию, используется значение Framework20SP1, если в файле конфигурации в элементе httpRuntime в атрибуте targetFramework не указано значение "4.5":
<httpRuntime targetFramework="4.5" />
В этом случае в качестве MachineKeyCompatibilityMode будет использоваться Framework45 и VIEWSTATE будут шифроваться по умолчанию.
IIS, который используется в Microsoft Exchange в качестве веб-движка, по умолчанию использует legacy версию .NET-фреймворка.
Вторая особенность: возможны случаи, когда после десериализации вредоносного VIEWSTATE событие Event ID 1316 не будет записано в журнал. Такое поведение определяется специфическим содержимым полезной нагрузки VIEWSTATE. В этом случае для детектирования можно использовать события механизма Event Tracing for Windows (ETW). В этом может помочь информация из двух событий сборки: AssemblyLoad_V1 (наступает, когда сборка загружается) и ModuleLoad_V2 (вызывается при загрузке модуля), примеры таких событий при загрузке вредоносной сборки находятся в Приложении II.
Для детекта можно использовать следующую логику: у вредоносной сборки в событии AssemblyLoad_V1 в поле FullyQualifiedAssemblyName значение PublicKeyToken обычно равно null, так как чаще всего вредоносные сборки не подписаны, а в событии ModuleLoad_V2 в поле ModuleILPath будет только имя сборки, а не полный путь до файла.
Сборка |
AssemblyLoad_V1 |
ModuleLoad_V2 ModuleILPath |
Легитимная |
PublicKeyToken=31bf3856ad364e35 |
C:\\Windows\\Microsoft.Net\\assembly\\GAC_MSIL\\System.Runtime.InteropServices.RuntimeInformation\\v4.0_4.0.0.0__b03f5f7f11d50a3a\\System.Runtime.InteropServices.RuntimeInformation.dll |
Вредоносная |
null |
Microsoft.Exchange.Management.Powershell.Support |
Стоит уделять особое внимание анализу имени сборки, так как у злоумышленников есть возможность изменить его вручную с целью сделать его похожим на путь.
Мы подготовили Sigma правило, детектирующее загрузку вредоносных in-memory сборок, его можно найти в разделе IOCs.
Митигация. Machine Key
После обнаружения атаки с помощью десериализации VIEWSTATE, необходимо заменить Machine Key, так как сам факт успешной атаки означает, что ключи валидации и расшифровки были скомпрометированы.
Замена вручную созданных ключей
В случае использования обычных (не автоматически сгенерированных) ключей процедуру замены можно выполнить прямо в IIS Manager на вкладке Machine Key:
Для этого необходимо выполнить следующие действия:
Generate Keys → Apply для атакованных приложений (рекомендуется сделать это для всех приложений веб-сервера).
Обязательно перезапустить службу IIS (или весь сервер), так как worker-процесс будет использовать «старые» ключи из памяти:
net stop w3svc net start w3svc |
Смену ключей необходимо делать в первую очередь для тех приложений, которые атакуют злоумышленники и которые доступны извне без аутентификации (например, owa и ecp для Exchange). В общем случае рекомендуется сделать смену ключей для всех приложений веб-сервера для большей безопасности (например, через настройки ключей для всего сайта), так как чаще всего проблематично понять, какими ключами обладает злоумышленник.
Значения ключей, заданные для сайта, распространяются на все его приложения только в том случае, если у приложения не заданы свои настройки ключей. Это необходимо учитывать при смене ключей.
Замена автоматически сгенерированных ключей
В случае использования автоматически сгенерированных ключей процедура их замены выглядит нетривиально хотя бы потому, что в публичном пространстве практически нет инструкций на эту тему. В то же время во многих статьях по настройке IIS рекомендуют использовать именно такие ключи, а значит, проблема их замены может иметь массовый характер. Также auto generated-ключи используются по умолчанию во всех приложениях Exchange после установки.
В случае использования автоматически сгенерированных ключей вкладка Machine Key в IIS Manager выглядит так:
Поля, в которых ранее были указаны значения ключей, не редактируются и не содержат самих значений. Чтобы понять, как их заменить, нужно исследовать механизм их генерации и хранения.
В результате глубокого исследования исходного кода, мы выяснили, что ключи генерируются при каждом новом запуске приложения (при старте домена) на основе мастер-ключа из реестра, который после самой первой генерации (скорее всего, после установки IIS/Exchange или первого использования генерации) не меняется. Происходит это в два этапа:
генерация мастер-ключа, который хранится в реестре;
runtime-генерация Validation и Decryption ключей.
Мастер-ключ хранится в реестре и в дальнейшем используется при генерации ключей, которые уже непосредственно участвуют в валидации и расшифровке. Сами они хранятся в памяти worker-процесса. Результаты нашего исследования местоположения ключей представлены в таблице:
Identity |
Master key registry path |
Encrypted |
LocalSystem |
HKLM\SECURITY\Policy\Secrets\L$ASP.NETAutoGenKeysV44.0.30319.0\ |
Yes |
ApplicationPoolIdentity |
HKU\S-1-5-82-<Virt_Acc_SID>\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0:AutoGenKeyV4 |
No |
LocalService |
HKU\S-1-5-19\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0:AutoGenKeyV4 |
No |
NetworkService |
HKU\S-1-5-20\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0:AutoGenKeyV4 |
No |
Custom Account |
HKU\S-1-5-21-<USER_SID>\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0:AutoGenKeyV4 |
No |
With Load User Profile False or |
HKLM\SOFTWARE\Microsoft\ASP.NET\4.0.30319.0\AutoGenKeys\<USER_SID>:AutoGenKeyV4 |
No |
Identity – пользователь, от которого запускается worker-процесс, он задается в разделе Process Model в настройках пула на вкладке Application Pools в IIS Manager:
Как видно из таблицы, при работе worker-процесса с правами LocalSystem, мастер-ключ хранится в реестре в зашифрованном виде.
Таким образом, алгоритм замены ключей выглядит следующим образом:
Удалить все значения из соответствующего ключа реестра (см. таблицу выше). Это позволит при следующем запуске worker-процесса записать новые сгенерированные значения, тем самым обновив мастер-ключ.
Выполнить перезапуск worker-процесса или всего сервера, аналогично алгоритму для вручную созданных ключей (net stop w3svc). Пока он не будет перезапущен, worker-процесс будет использовать старые значения ключей.
На рисунке показано, что нужно удалять для разных типов Identity.
Отметим, что для защиты от атак десериализации можно также завести все IIS-серверы за VPN, чтобы у злоумышленников не было возможности отправить запрос с вредоносным VIEWSTATE.
Заключение
Несмотря на то, что уязвимость десериализации VIEWSTATE существует уже более 10 лет, мы видим, что с 2020 года началась и продолжается её активная эксплуатация в дикой среде различными группировками, в том числе Obstinate Mogwai. Обнаружить эксплуатацию этой уязвимости — задача нетривиальная. Детектировать инцидент приходится буквально по одному событию журнала Windows, которое в определенных обстоятельствах может и не записываться. Кроме того, атакуемые процессы на серверах Exchange работают с высочайшими привилегиями (worker‑процесс Exchange работает с правами LocalSystem), что делает эту уязвимость ещё более опасной.
Надеемся, что в нашей статье вы нашли много полезной информации об уязвимости десериализации VIEWSTATE и способах детектирования подобных атак. Ниже представлены индикаторы компрометации.
Кстати, Obstinate Mogwai — не единственная группировка, использующая уязвимость десериализации VIEWSTATE. В последние месяцы мы видели как минимум ещё одну. Подробнее о ней расскажем в нашем блоге Solar 4RAYS. Следите за обновлениями!
Ознакомиться с другими исследованиями в блоге 4RAYS можно по ссылке.
IOCs
File hashes
MD5
41a15b8d3d8c840be37690f8353e8934
6c63601e9c115c0e7ff3220e023b33bb
817c8c15040261490b75d5476f8ba5d6
SHA1
41b543f397e77461dee196b830c30024dc20605d
b4d3db052fa682abd38218620c27351766275911
348dbaa262410684153228a904c60e0d9cc17014
SHA256
503275fbf9bcd6575a6f8a014c903727eb28f2d77f067082fcf4f60c2ca630f5
06240b9dfb75b8a430c7c34cbb13cd066acf7f0e1d889891f576d7f4bc999c15
4608df9207e6612bcc548d0db39a2d03ed74c9c0f30c696a3a6ef2cc792c250a
IP
IP-адреса, с которых наблюдалась эксплуатация уязвимости десериализации VIEWSTATE.
45.12.67[.]18
77.223.109[.]162
77.223.109[.]163
77.223.109[.]164
77.223.109[.]165
193.47.34[.]229
Sigma
Правило для обнаружения загрузки вредоносных in memory only сборок в память
title: Detect Malicious Assembly and Module Load Events id: 2617e7ed-adb7-40ba-b0f3-8f9945fe6c09 status: description: Detects suspicious Assembly and Module Load events indicative of potentially malicious activity. references: author: 4rays date: modified: logsource: product: SilkETW detection: assemblywithoutsignature: EventName: 'Loader/AssemblyLoad' XmlEventData.FullyQualifiedAssemblyName|contains: 'PublicKeyToken=null' modulewithoutpath: EventName: 'Loader/ModuleLoad' XmlEventData.ModuleILPath|re: '\s"[^\\]+"' condition: assemblywithoutsignature and modulewithoutpath falsepositives: - Legitimate changes to assemblies and modules. level: high |
Yara
rule mem_apt_obstinent_mogway_viewstate_assemblies {
meta:
description = "detects malicious assemblies loaded in memory via viewstate deserialization using leaked keys"
author = "SOLAR 4RAYS"
date = "20240423"
hash = "41a15b8d3d8c840be37690f8353e8934" // execute powershell cmd
hash = "6c63601e9c115c0e7ff3220e023b33bb" // upload file to C2
hash = "817c8c15040261490b75d5476f8ba5d6" // fileview
strings:
// xor key used in viewstate payloads
$ = { 45 AF 33 27 56 DF CA BB 12 67 9A 52 63 B8 2B C7 62 1F 10 8D 15 }
condition:
any of them
}
MITRE
Техника, тактика |
Процедура |
Initial Access |
APT-группировка Obstinent Mogway (далее - злоумышленник) эксплуатировала уязвимость десериализации VIEWSTATE для удаленного выполнения кода на Exchange-серверах жертвы |
Execution |
Злоумышленник выполнял powershell-команды путем загрузки .NET-сборок в память процесса w3wp.exe в результате эксплуатации уязвимости десериализации VIEWSTATE, а также запускал .NET RAT в памяти путем запуска powershell скрипта |
Persistence |
Злоумышленник в начале инцидента использовал различные вебшеллы для выполнения команд и закрепления |
Defense Evasion |
Злоумышленник загружал вредоносные .NET-сборки в память процесса w3wp.exe |
Defense Evasion |
Злоумышленник загружал .NET RAT в процесс powershell.exe |
Defense Evasion |
Злоумышленник назвал .NET-сборки именами, мимикрирующими под легитимные .NET-сборки для дополнительной скрытности |
Defense Evasion T1070.004 – Indicator Removal on Host: File Deletion |
Злоумышленник удалял свои файлы и архивы с данными |
Credential Access |
Злоумышленник включал настройку в реестре для упрощения дампа аутентификационного материала в открытом виде |
Discovery |
Злоумышленник проверял настройки реестра через утилиту reg.exe |
Discovery |
Злоумышленник получал содержимое интересующих его каталогов с помощью Get-ChildItem команд |
Discovery |
Злоумышленник выполнял команды net user |
Command and Control |
Злоумышленник отправлял POST-запросы с вредоносными нагрузками VIEWSTATE |
Command and Control |
Злоумышленник в POST-запросах отправлял зашифрованные команды (xor + base64) и получал результаты их выполнения в xor-зашифрованном виде |
Приложение I – .NET-сборки из памяти
name |
md5 |
comment |
Microsoft.Exchange.Management.Powershell.Support.dll |
41a15b8d3d8c840be37690f8353e8934 |
Ps-сборка из памяти w3wp.exe |
Microsoft.Exchange.UM.UMCommon.dll |
817c8c15040261490b75d5476f8ba5d6 |
FileView-сборка из памяти w3wp.exe |
Microsoft.Exchange.MessageSecurity.dll |
6c63601e9c115c0e7ff3220e023b33bb |
FileDown-сборка из памяти w3wp.exe |
Приложение II – Примеры событий при загрузке вредоносной сборки
AssemblyLoad_V1
{ "ProviderGuid": "e13c0d23-ccbc-4e12-931b-d9cc2eee27e4", "YaraMatch": [], "ProviderName": "Microsoft-Windows-DotNETRuntime", "EventName": "Loader/AssemblyLoad", "Opcode": 37, "OpcodeName": "AssemblyLoad", "TimeStamp": <REDACTED>", "ThreadID": 12268, "ProcessID": 15308, "ProcessName": "w3wp", "PointerSize": 8, "EventDataLength": 238, "XmlEventData": { "FormattedMessage": "AssemblyID=2,209,623,677,488;\r\nAppDomainID=2,209,468,624,928;\r\nAssemblyFlags=0;\r\nFullyQualifiedAssemblyName=0;\r\nClrInstanceID=Microsoft.Exchange.Management.Powershell.Support, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null ", "ProviderName": "Microsoft-Windows-DotNETRuntime", "ClrInstanceID": "29", "AppDomainID": "2,209,468,624,928", "BindingID": "0", "MSec": "7581.5038", "AssemblyID": "2,209,623,677,488", "PID": "15308", "TID": "12268", "AssemblyFlags": "0", "PName": "", "FullyQualifiedAssemblyName": "Microsoft.Exchange.Management.Powershell.Support, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "EventName": "Loader/AssemblyLoad" } } |
ModuleLoad_V2
{ "ProviderGuid": "e13c0d23-ccbc-4e12-931b-d9cc2eee27e4", "YaraMatch": [], "ProviderName": "Microsoft-Windows-DotNETRuntime", "EventName": "Loader/ModuleLoad", "Opcode": 33, "OpcodeName": "ModuleLoad", "TimeStamp": "<REDACTED>", "ThreadID": 12268, "ProcessID": 15308, "ProcessName": "w3wp", "PointerSize": 8, "EventDataLength": 170, "XmlEventData": { "ModuleID": "140,735,524,732,376", "ManagedPdbSignature": "00000000-0000-0000-0000-000000000000", "Reserved1": "0", "ManagedPdbBuildPath": "", "ModuleNativePath": "", "NativePdbBuildPath": "", "FormattedMessage": "ModuleID=140,735,524,732,376;\r\nAssemblyID=2,209,625,905,760;\r\nModuleFlags=Manifest;\r\nModuleILPath=0;\r\nModuleNativePath=Microsoft.Exchange.Management.Powershell.Support;\r\nClrInstanceID=;\r\nManagedPdbSignature=29;\r\nManagedPdbAge=00000000-0000-0000-0000-000000000000;\r\nManagedPdbBuildPath=0;\r\nNativePdbSignature=;\r\nNativePdbAge=00000000-0000-0000-0000-000000000000;\r\nNativePdbBuildPath=0 ", "MSec": "9847.4270", "NativePdbAge": "0", "ModuleFlags": "Manifest", "AssemblyID": "2,209,625,905,760", "PID": "15308", "NativePdbSignature": "00000000-0000-0000-0000-000000000000", "ModuleILPath": "Microsoft.Exchange.Management.Powershell.Support", "TID": "12268", "ManagedPdbAge": "0", "ProviderName": "Microsoft-Windows-DotNETRuntime", "PName": "", "ClrInstanceID": "29", "EventName": "Loader/ModuleLoad" } } |
Авторы: команда цифровой криминалистики и реагирования на инциденты (DFIR) центра исследования киберугроз Solar 4RAYS