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

Статья Patch-gapping Google Chrome

sploitem

HDD-drive
Пользователь
Регистрация
18.05.2020
Сообщения
23
Реакции
26
Patch-gapping в Google Chrome

9 сентября 2019.

Patch-gapping (можно перевести как разрыв между патчем и предыдущей версией или разница патча) это техника обнаружения и эксплуатации исправленных уязвимостей в проектах с открытым исходным кодом. Дело в том, что исправление выходит раньше, чем конечные пользователи получают обновление в виде свежего релиза. Временной интервал между исправлением и его получением потребителями может варьироваться от нескольких дней до нескольких месяцев.

Все чаще это рассматривается, как серьезная угроза с возможностью использования в так называемой дикой природе (in-the-wild). Подобные случаи засекает Google (https://googleprojectzero.blogspot.com/2019/08/jsc-exploits.html).

Background

Команда exodus, в середине августа, нашла в списке изменений CL 1751971, ссылка на который не работает, интересный баг, связанный с sealed(запечатанный) и frozen(замороженный) объектами. В том числе обратный тест (regression) или тест на предыдущие баги, который приводил к ошибке сегментации. Потом эти изменения забросили, в последствие и вовсе удалили. Работу продолжили с CL 1760976 (https://chromium-review.googlesource.com/c/v8/v8/+/1760976), который повлек еще больше изменений.

Поскольку исправление было слишком сложным, временным решением было отключить эту функциональность в ветке 7.7 (https://chromium.googlesource.com/v8/v8.git/+log/7.7-lkgr). Выпуск релиза с этим отключением был намечен на 10-е сентября 2019. То же изменение было сделано в ветке 7.6 (https://chromium.googlesource.com/v8/v8.git/+log/7.6-lkgr). Но оно было сделано уже после выпуска обновления stable channel 26-го августа 2019 (https://chromereleases.googleblog.com/2019/08/stable-channel-update-for-desktop_26.html). Соответственно не попало в релиз. Поэтому последний стабильный релиз Chrome оставался подверженным багу.

Эти обстоятельства сделали баг (суть – уязвимость, ибо вызывает segmentation fault) идеальным кандидатом на разработку 1day эксплойта.

Сообщение коммита описательное, проблема есть результат воздействия Object.preventExtensions и Object.seal/freeze на карты (maps) и элемент хранилища объектов и того, как некорректные переходы по карте отслеживаются v8 при некоторых условиях. Так как отслеживание карт в v8 является сложной темой, обсудим только те детали, которые необходимы для понимания уязвимости. Информацию по соответствующим темам можно найти по следующим ссылкам:

https://docs.google.com/document/d/1X6zO5F_Zojizn2dmo_ftaOWsY8NltPHUhudBbUzMxnc/preview
https://v8.dev/blog/fast-properties
https://v8.dev/blog/react-cliff



Расположение объектов в v8

JS движки реализуют несколько оптимизаций хранилища свойств объектов. Распространенная техника – использовать раздельные хранилища для целочисленных ключей (часто называются элементами) и для строковых\символьных ключей (обычно называются слоты или именованные свойства). Это позволяет им использовать непрерывные массивы для свойств с целочисленными ключами, где индекс напрямую соотносится (maps) с хранилищем, что ускоряет доступ. Строковые значения так же хранятся в массиве, но для получения индекса соответствующего ключа, нужны обходные пути. Эта информация хранится в карте (map) или спрятанном классе – HiddenCLass объекта.

Хранение форм объекта в спрятанном классе — это еще одна попытка сократить размер используемой памяти. Классы HiddenClass похожи на классы в объектно-ориентированных языках программирования. Тем не менее, из-за того, что невозможно знать наперед конфигурацию объектов в языках, основанных на прототипах, таких как JavaScript, они создаются по требованию. JS движки создают один спрятанный класс для заданной формы, которую делят объекты, имеющие одинаковую структуру. Добавление именованного свойства в объект влечет за собой создание нового спрятанного класса, который содержит информацию про все предыдущие свойства и для нового, затем обновляется карта объекта, как показано ниже (источник - https://v8.dev/blog/fast-properties).

1594827220669.png


Эти переходы сохраняются в цепочке спрятанных классов, к которой обращаются при создании нового объекта с такими же свойствами или добавляются свойства в таком же порядке. Если там есть такой же переход, он используется повторно, иначе – создается новый спрятанный класс и добавляется в дерево переходов.

1594827237789.png


Сами по себе свойства могут храниться в трех местах. Самое быстрое это хранилище внутри объекта, которому требуется только найти ключ в спрятанном классе, чтобы найти индекс в хранилище. Это ограничивается определенным числом свойств, остальные хранятся в так называемом быстром хранилище, которое представляет собой отдельный массив (FixedArray), на который указывает поле объекта свойства, как показано ниже.

1594827257990.png


Если у объекта добавляется и удаляется много свойств, то становится накладно работать со спрятанными классами. V8 использует эвристику для выявления таких случаев и переводит объект в разряд медленного, использующего в качестве хранилища словарь (Properties Dict), как показано на следующей диаграмме.

1594827290552.png


Еще одна распространенная оптимизация — это хранение элементов с целочисленными ключами в плотном или запакованном формате, если они умещаются в конкретное представление, например маленькие целые или с плавающей точкой (int, float). Это позволяет избежать обычной упаковки значений в движках – хранение чисел, как указателей на объекты чисел, тем самым сохраняя память и ускоряя операции над массивами. V8 имеет несколько таких видов элементов, например PACKED_SMI_ELEMENTS, который обозначает непрерывный массив элементов с маленькими целыми числами. Этот вид хранилища отслеживается в карте объекта и должен все время обновляться (вид\тип хранилища), чтобы избежать путаницы (confusion). Виды элементов организованы в виде решетки, переходы разрешаются только более общим типам данных. Это значит, что, например, добавление числа с плавающей точкой в объект с PACKED_SMI_ELEMENTS, конвертирует каждое значение в double, установит новое добавленное значение и поменяет тип элементов на PACKED_DOUBLE_ELEMENTS.


preventExtensions, seal and freeze

JS имеет несколько способов избежать вышеописанной ситуации.
  • Object.preventExtensions – предотвращает добавление новых свойств в объект.
  • Object.seal – предотвращает добавление новых свойств в объект, а также предотвращает изменение имеющихся свойств (изменяет атрибуты writable, enumerable или configurable).
  • Object.freeze – то же самое, а также предотвращает изменения значений свойств тем самым запрещая любые изменения объекта.



Анализ PoC

Уязвимость существует потому, что v8 отслеживает переходы карты в определенных случаях без обновления хранилища элемента соответственно, что может привести к широкомасштабным последствиям.

JavaScript:
1.    // Based on test/mjsunit/regress/regress-crbug-992914.js
2.
3.    function mainSeal() {
4.    const a = {foo: 1.1}; // a has map M1
5.    Object.seal(a); // a transitions from M1 to M2 Map(HOLEY_SEALED_ELEMENTS)
6.
7.    const b = {foo: 2.2}; // b has map M1
8.    Object.preventExtensions(b); // b transitions from M1 to M3 Map(DICTIONARY_ELEMENTS)
9.    Object.seal(b); // b transitions from M3 to M4
10.    const c = {foo: Object} // c has map M5, which has a tagged `foo` property, causing the maps of `a` and `b` to be deprecated
11.    b.__proto__ = 0; // property assignment forces migration of b from deprecated M4 to M6
12.
13.    a[5] = 1; // forces migration of a from the deprecated M2 map, v8 incorrectly uses M6 as new map without converting the backing store. M6 has DICTIONARY_ELEMENTS while the backing store remained unconverted.
14.    }
15.
16.    mainSeal();

В коде, два объекта, a и b создаются с идентичной структурой. Затем объект a запечатывается (sealed), у объекта b вызываются методы preventExtensions() и seal(). Это вынуждает объект a переключиться на карту с типом элементов HOLEY_SEALED_ELEMENTS, b переводится в медленное хранилище, получая карту с типом элементов DICTIONARY_ELEMENTS.

Уязвимость тригеррится на строках 10-13. Строка 10 создает объект c, который имеет свойство foo, у объекта c структура несовместима с предыдущими структурами объектов. Поэтому для объекта c создается новая карта со свойством foo, карты для объектов a и b помечаются, как устаревшие. Это означает, что эти объекты мигрируют на новые карты при добавлении им свойств. Строка 11 тригеррит переход для объекта b, строка 13 – для объекта a. Проблема в том, что v8 ошибочно предполагает, что объект a может мигрировать в ту же карту что и b, но не конвертирует резервное хранилище. Это вызывает путаницу в типах (type confusion) FixedArray и NumberDictionary.

(Прим. Пер.)

Карта M1 это FixedArray.


1594827318976.png


В строке 13 к объекту a применяется карта M6, это тип элементов DICTIONARY_ELEMENTS. В объекте a будет словарь NumberDictionary(Properties Dict).

1594827333489.png


Происходит это из-за того, что v8 теряет в цепочке переходов нужные карты и делает миграцию объекта к карте объекта b, который имеет такую же конфигурацию, как и объект a.

Но то, что было в объекте a, до добавления нового свойства не конвертируется. А новое свойство имеет тип элементов DICTIONARY_ELEMENTS. Получается объект a состоит из смеси типов. Но v8 будет думать, что он состоит из NumberDictionary. А интерпретация FixedArray как NumberDictionary ведет к ошибкам.




Эксплуатация

Уязвимость можно превратить в примитив чтения\записи в произвольную память используя путаницу типов, как описано выше. С её помощью можно повредить длину массива, потом использовать этот массив для дальнейшего повреждения других TypedArray. Дальше это можно раскрутить до выполнения произвольного кода в процессе рендерера.

Расположение (структура) в памяти классов FixedArray и NumberDictionary.

FixedArray это класс C++, используется, как хранилище свойств в нескольких JS объектах. Имеет простое расположение в памяти, как показано ниже, с атрибутом указатель на карту, атрибут длина (хранится как v8 маленькое целое (31-битное целое, сдвинутое влево на 32)), затем идут сами элементы.

JavaScript:
pwndbg> job 0x065cbb40bdf1
0x65cbb40bdf1: [FixedDoubleArray]
map: 0x1d3f95f414a9
length: 16
0: 0.1
1: 1
2: 2
3: 3
4: 4
…
pwndbg> tel 0x065cbb40bdf0 25
00:0000   0x65cbb40bdf0 -> 0x1d3f95f414a9 <- 0x1d3f95f401
01:0008   0x65cbb40bdf8 <- 0x1000000000
02:0010   0x65cbb40be00 <- 0x3fb999999999999a
03:0018   0x65cbb40be08 <- 0x3ff0000000000000
04:0020   0x65cbb40be10 <- 0x4000000000000000

Класс NumberDictionary реализует хэш-таблицу с целочисленным ключом поверх класса FixedArray. Расположение в памяти показано ниже. Он содержит четыре дополнительных атрибута помимо карты и длины.

  • Elements – число элементов хранящихся в словаре.
  • Deleted – число удаленных элементов.
  • Capacity – число элементов, которое может вместить словарь.
  • Max number key index – наибольший ключ сохраненный в словаре.
Уязвимость позволяет присвоить этим четырем атрибутам произвольные значения пока это FixedArray (до type confusion), затем вызвать путаницу типов и интерпретировать эти значения как поля NumberDictionary.

JavaScript:
pwndbg> job 0x2d7782c4bec9
0x2d7782c4bec9: [NumberDictionary]
- map: 0x0c48e8bc16d9 <Map>
- length: 28
- elements: 4
- deleted: 0
- capacity: 8
- elements: {
0: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
1: 0 -> 16705
2: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
3: 1 -> 16706
4: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
5: 0x0c48e8bc04d1 <undefined> -> 0x0c48e8bc04d1 <undefined>
6: 2 -> 16707
7: 3 -> 16708
}

pwndbg> tel 0x2d7782c4bec9-1 25
00:0000   0x2d7782c4bec8 -> 0xc48e8bc16d9 <- 0xc48e8bc01
01:0008   0x2d7782c4bed0 <- 0x1c00000000
02:0010   0x2d7782c4bed8 <- 0x400000000
03:0018   0x2d7782c4bee0 <- 0x0
04:0020   0x2d7782c4bee8 <- 0x800000000
05:0028   0x2d7782c4bef0 <- 0x100000000
06:0030   0x2d7782c4bef8 -> 0xc48e8bc04d1 <- 0xc48e8bc05
...
09:0048   0x2d7782c4bf10 <- 0x0
0a:0050   0x2d7782c4bf18 <- 0x414100000000
0b:0058   0x2d7782c4bf20 <- 0xc000000000
0c:0060   0x2d7782c4bf28 -> 0xc48e8bc04d1 <- 0xc48e8bc05
...
0f:0078   0x2d7782c4bf40 <- 0x100000000
10:0080   0x2d7782c4bf48 <- 0x414200000000
11:0088   0x2d7782c4bf50 <- 0xc000000000

Элемент в NumberDictionary по размеру, как три слота в FixedArray. Например, элемент с ключом 0 начинается по адресу 0x2d7782c4bf10, как показано выше. Сначала идет ключ, потом значение, в данном случае маленькое целое 0x4141, потом идет PropertyDescriptor обозначающий атрибуты свойства: configurable, writable, enumerable. Значение 0xc000000000 означает, что все три атрибута установлены.

Уязвимость позволяет контролировать все поля NumberDictionary, кроме поля длины, устанавливая их значения до вызова путаницы типов, через FixedArray (карта M1).

Самое интересное поле это ёмкость словаря – capacity т.к. оно используется при расчете границ. При попытках получить, присвоить или удалить элемент, вызывается функция HashTable::FindEntry, которая ищет расположение элемента по ключу.

C++:
// Find entry for key otherwise return kNotFound.
template <typename Derived, typename Shape>
int HashTable<Derived, Shape>::FindEntry(ReadOnlyRoots roots, Key key,
            int32_t hash) {
    uint32_t capacity = Capacity();
    uint32_t entry = FirstProbe(hash, capacity);
    uint32_t count = 1;
    // EnsureCapacity will guarantee the hash table is never full.
    Object undefined = roots.undefined_value();
    Object the_hole = roots.the_hole_value();
    USE(the_hole);
    while (true) {
        Object element = KeyAt(entry);
        // Empty entry. Uses raw unchecked accessors because it is called by the
        // string table during bootstrapping.
        if (element == undefined) break;
        if (!(Shape::kNeedsHoleCheck && the_hole == element)) {
            if (Shape::IsMatch(key, element)) return entry;
        }
        entry = NextProbe(entry, count++, capacity);
    }
    return kNotFound;
}

Хэш таблицы в v8 используют квадратичное зондирование с рандомным хэш-семенем. Это означает, что аргумента int32_t hash и конкретное расположение словарей в памяти будет меняться между запусками. Функции FirstProbe и NextProbe используются для определения расположения значения. Их аргументы — это ёмкость словаря, а значит и подконтрольная атакующему величина.

C++:
inline static uint32_t FirstProbe(uint32_t hash, uint32_t size) {
    return hash & (size - 1);
}

inline static uint32_t NextProbe(uint32_t last, uint32_t number, uint32_t size) {
    return (last + number) & (size - 1);
}


Емкость — это число степени двойки в нормальных условиях и поэтому маскировка зондов при помощи capacity – 1 позволяет ограничить диапазон доступа значениями в границах (in-bounds). Тем не менее, при передаче бОльшего значения при помощи уязвимости позволяет получить доступ вне границ (out-of-bounds) с разными смещениями. Это легко может привести к падениям т.к. v8 попытается интерпретировать любое нечетное значение как помеченный указатель (tagged pointer in v8).

Возможное решение — это установить значение емкости равное k, которое находится вне границ и является степенью двойки плюс 1. Это заставит FindEntry посетить два возможных места, на смещении 0, и на смещении k (три раза). С осторожными отступами (padding), целевой массив может быть размещен за словарем, поле длина которого как раз на этом смещении. Удаление словаря с ключом по адресу там же, где и длина массива, приведет к замене длины пустым значением. Пустое значение является валидным указателем на статичный объект, а также большим значением, что может использоваться для чтения и записи за границами массива (out-of-bounds).

В то время, как этот метод может сработать, он не детерминированный из-за рандомизации и деградации структуры NumberDictionary. Но неудачи не вызывают падений Chrome и легко засекаются; перезагрузка страницы повторно инициализирует хэш-семя так что эксплойт может предпринять любое количество попыток.



Выполнение произвольного кода.

Следующее расположение объектов в памяти используется для получения чтения\записи в памяти процесса:
  • o: объект через который тригеррится уязвимость.
  • padding: массив, использующийся для выравнивания, чтобы целевой массив оказался на нужном смещении от объекта o.
  • float_array – целевой массив для повреждения поля length через out-of-bounds удаление свойства объекта o.
  • tarr – TypedArray используемый для повреждения следующего TypedArray.
  • aarw_tarr – TypedArray использующийся для доступа к произвольной памяти.
  • obj_addrof – объект через который происходит утечка информации об адресе произвольного JS объекта.

Эксплойт достигает исполнения кода при помощи следующих шагов:
  • Создать расположение объектов, описанное выше.
  • Затригеррить уязвимость, переписать длину float_array через удаление свойства o. Если неудача, то перезапустить эксплойт перезагрузив страницу.
  • Перезаписать длину tarr для повышения надежности т.к. долгое использование поврежденного float_array может вызвать проблемы.
  • Перезаписать хранилище свойств массива aarw_tarr и использовать его для получения доступа на чтения\запись в адресном пространстве.
  • Загрузить модуль WebAssembly. Это загружает в адресное пространство кусок памяти с разрешениями на чтение, запись и выполнение кода, размером 4КБ.
  • Через obj_addrof получить адрес экспортируемой функции модуля WebAssembly (rwx memory region).
  • Перезаписать код этой функции шеллкодом и запустить его, вызвав эту функцию.
Полный код эксплойта находится тут - https://github.com/exodusintel/Chro...it/blob/master/chrome_992914/chrome_992914.js и он позволяет лучше понять описанный процесс получения произвольного выполнения кода.

(https://github.com/exodusintel/Chro...nfusion-RCE-Exploit/tree/master/chrome_992914).

Для побега из песочницы понадобится отдельная уязвимость.

Демонстрация эксплойта (Youtube)


Перевод специально для xss.pro
Автор перевода - sploitem


Источник - https://blog.exodusintel.com/2019/09/09/patch-gapping-chrome/
 
Последнее редактирование:
я-то думал ты свою статью написал) но все-равно лайк
 
Последнее редактирование модератором:
Могу и свою. А смысл?
Смысл в том, что в жизни всем все всегда возвращается. сегодня ты помог кому-то, завтра тебе кто-то.
Не нужно чураться тех, кто слабее.
Все когда-то начинали
 
Уязвимость нулевого дня Google Chrome и Microsoft Edge опубликовали в Twitter
Исследователь безопасности Раджвардхан Агарвал обнаружил уязвимость нулевого дня для удаленного выполнения кода, которая работает в текущих версиях Google Chrome и Microsoft Edge.
ИБ-исследователь выпустил рабочий эксплойт для проверки концепции (PoC) для уязвимости движка JavaScript V8 в браузерах на основе Chromium. Он также заявил, что эта уязвимость уже исправлена в последней версии движка, но неясно, когда Google развернет его в Chrome
Когда HTML-файл PoC и соответствующий ему файл JavaScript загружаются в браузер на основе Chromium, они используют уязвимость для запуска программы калькулятора Windows
Однако, чтобы эксплойт работал, его необходимо связать с другой уязвимостью, которая позволит обойти песочницу Chromium
Чтобы протестировать эксплойт, журналисты BleepingComputer запустили последние стабильные версии браузеров Edge 89.0.774.76 и Chrome 89.0.4389.114 с флагом --no-sandbox, который отключает песочницу Chromium
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Уязвимость нулевого дня Google Chrome и Microsoft Edge опубликовали в Twitter
Исследователь безопасности Раджвардхан Агарвал обнаружил уязвимость нулевого дня для удаленного выполнения кода, которая работает в текущих версиях Google Chrome и Microsoft Edge.
ИБ-исследователь выпустил рабочий эксплойт для проверки концепции (PoC) для уязвимости движка JavaScript V8 в браузерах на основе Chromium. Он также заявил, что эта уязвимость уже исправлена в последней версии движка, но неясно, когда Google развернет его в Chrome
Когда HTML-файл PoC и соответствующий ему файл JavaScript загружаются в браузер на основе Chromium, они используют уязвимость для запуска программы калькулятора Windows
Однако, чтобы эксплойт работал, его необходимо связать с другой уязвимостью, которая позволит обойти песочницу Chromium
Чтобы протестировать эксплойт, журналисты BleepingComputer запустили последние стабильные версии браузеров Edge 89.0.774.76 и Chrome 89.0.4389.114 с флагом --no-sandbox, который отключает песочницу Chromium
Угу через Patch Gapping) 0day c pwn2own
 


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