Загрязненный — значит опасный: про уязвимость Prototype Pollution

0d2a76f8c9feab75e462be22b9d61e8f.jpg

Prototype Pollution (CVE-2023–45811, CVE-2023–38894, CVE-2019–10744) — не новая брешь, вы уже наверняка читали про нее и на Хабре, и на PortSwigger, и даже в научных журналах, но есть нюанс. Несмотря на большое количество публикаций, некоторые популярные решения до сих пор остаются уязвимыми для нее. Очередной пациент — библиотека на TypeScript @clickbar/dot-diver.Уязвимость CVE-2023–45827 исправлена в версии 1.0.2 и выше, поэтому мы со спокойной душой расскажем, что могло произойти с вашим продуктом, но, к счастью, не произошло.

Под катом читайте о том, как нужно было пользоваться библиотекой, чтобы точно столкнуться с уязвимостью Prototype Pollution. Мы, кстати, писали про нее в своем телеграм-канале POSIdev — там свежие новости про безопасную разработку, AppSec, а также регулярные обзоры трендовых угроз и наша любимая рубрика «Пятничные мемы»!

Итак, поехали!

Немного о Prototype Pollution

В JavaScript все сущности являются объектами, включая функции и определения классов. Процесс наследования реализован через модель прототипов. Каждый объект в JavaScript связан с особым объектом, который называется прототипом. Объект наследует все свойства связанного прототипа. Для доступа к прототипу объекта используется встроенное поле __proto__. Поиск любого поля в объекте производится по цепочке прототипов (prototype chain): сначала это поле ищется у объекта, потом у прототипа и далее — до самого верхнего уровня наследования.

Prototype Pollution позволяет атакующему «загрязнить» поле глобального объекта, которое может наследоваться пользовательскими объектами и создавать угрозу для безопасности приложения.

Основные условия успешной реализации атаки Prototype Pollution:

  1. Недоверенные входные данные, которые используются для «загрязнения» глобального объекта (prototype pollution source).

  2. Наличие уязвимых функций, которые могут приводить к проблемам безопасности в коде (sink).

  3. Возможность использования «загрязненного» поля глобального объекта без его фильтрации в уязвимой функции (exploitable gadget).

Механизм реализации атаки выглядит так:

  1. Атакующий «загрязняет» свойство глобального объекта через доступный объект.

  2. В приложении для уязвимой функции используется объект, который наследуется от «загрязненного» глобального объекта.

  3. Уязвимая функция использует поле из «загрязненного» глобального объекта, заданное атакующим, что приводит к нарушению безопасности.

Основные поля, используемые в атаках:

Уязвимость в dot-diver

Библиотека @clickbar/dot-diver, написанная на TypeScript (source code), предоставляет удобный API для чтения значения поля из объекта (с помощью функции getByPath) и записи значения в поле объекта (с помощью функции setByPath).

Уязвимость была обнаружена в функции setByPath, которая принимает три аргумента:

  • object— объект, для свойства которого устанавливается значение,

  • path— шаблон пути до свойства, которое необходимо изменить,

  • value— значение, которое будет в поле по указанному пути.

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

 Пример использования функции setByPath:

4eec7df846bf08a9600509d6bc7135b3.png

Основная проблема в применении этой функции заключается в том, что если значение пути содержит путь к прототипу, то появляется возможность установить свойства прототипа, а это может привести к «загрязнению» глобального объекта.

Код функции до исправления (версия 1.0.1):

c38863533132a4172b1955f814cf38cd.png

Этот же код после исправления (версия 1.0.2):

76cbf5e12f2102dd5b2b82ccd03b5f94.png

В исправлении была добавлена проверка наличия собственного поля в объекте (с помощью функции Object.prototype.hasOwnProperty) перед его изменением. В случае отсутствия поля вызывается исключение с соответствующим сообщением.

Пример эксплуатации

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

  • user — с правом внесения данных о прочитанных книгах и возможностью просмотра;

  • admin — с правом удаления книг из списка.

Разграничение доступа реализуется с помощью дополнительного поля isAdmin, которое есть только у объекта user с успешным результатом проверки учетных данных для роли admin. У других пользователей это поле отсутствует.

ffd1219845555ece8a337e3df47c8f8e.png

Фрагмент кода, выполняющий обработку запроса на удаление книг по ключу title и реализующий контроль прав доступа.

c0e07dd0786ca2473f9423b1041aaba4.png

В приложении используются следующие команды API:

$curl -v -X GET -H http://192.168.26.1:5000/

$curl -v -X PUT -H "Content-Type:application/json" --data '{"auth":{"name":"reader", "pwd":"books"},"title":"Tom Sawyer"}' http://192.168.26.1:5000/

$curl -v -X DELETE -H "Content-Type:application/json" --data '{"auth":{"name":"reader", "pwd":"books"},"title":"Tom Sawyer"}' http://192.168.26.1:5000/

После попытки удаления книги из списка от имени пользователя reader возвращается ответ с кодом 403 и текстом сообщения Access denied, сигнализирующий об отсутствии прав на выполнение таких действий.

В запрос добавляется полезная нагрузка для атаки:

$curl -v -X PUT -H "Content-Type:application/json" --data '{"auth":{"name":"reader", "pwd":"books"},"title":"Tom Sawyer", "note":"__proto__.isAdmin", "text":true}' http://192.168.26.1:5000/

В ответ на запрос отображается успешный результат обновления списка книг. Кроме того, у глобального объекта Object появилось поле isAdmin.

7bcfaacc09a5f087966128bcf77e1993.png

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

Меры противодействия

Основные рекомендации для устранения проблем безопасности, связанных с уязвимостью Prototype Pollution:

  • постоянное обновление пакетов до последних стабильных версий;

  • поиск уязвимых компонентов с помощью SCA и SAST;

  • обработка недоверенных пользовательских данных на входе приложения;

  • использование конструкторов в случае отсутствия необходимости в наследовании объектов:

    • let obj = Object.create(null),

    • let obj = {__proto__:null};

  • для защиты от модификации глобальных прототипов можно использовать функции API: Object.freeze() и Object.seal(). Но нужно учитывать, что они могут нарушить работу библиотек, в которых применяется механизм изменения атрибутов у глобальных прототипов.

4b9e823fefc047cd56e65fd32d7cd66d.pngАлександр Болдырев

Ведущий специалист группы экспертизы статического анализа приложений

Дополнительные материалы

Карточка CVE

ID-CVE

CVE-2023–45827

CVSS 3.1

9,8

ID-CWE

CWE-1321

Type

Prototype Pollution

Impact

Зависит от последующего использования свойств объекта для реализации критически опасных функций (разграничения прав, исполнения кода, бизнес-логики)

Description package

Библиотека на TypeScript @clickbar/dot-diver до версии 1.0.1 включительно, которая предоставляет удобный API для чтения значения поля из объекта (функция getByPath) и записи значения в поле объекта (функция setByPath)

© Habrahabr.ru