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

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

Prototype Pollution ( CVE-2023-45811CVE-2023-38894CVE-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. Уязвимая функция использует поле из «загрязненного» глобального объекта, заданное атакующим, что приводит к нарушению безопасности.

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

  • object.constructor.prototype.pullutedField
  • object.__proto__.pullutedField

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

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

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

  • object — объект, для свойства которого устанавливается значение,
  • path — шаблон пути до свойства, которое необходимо изменить,
  • value — значение, которое будет в поле по указанному пути.

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

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

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

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


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


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

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

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

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

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

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

В приложении используются следующие команды 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

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

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

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

  • постоянное обновление пакетов до последних стабильных версий;
  • поиск уязвимых компонентов с помощью SCA и SAST;
  • обработка недоверенных пользовательских данных на входе приложения;
  • использование конструкторов в случае отсутствия необходимости в наследовании объектов:
    • let obj = Object.create(null)
    • let obj = {__proto__:null}
  • для защиты от модификации глобальных прототипов можно использовать функции API: Object.freeze() и Object.seal(). Но нужно учитывать, что они могут нарушить работу библиотек, в которых применяется механизм изменения атрибутов у глобальных прототипов.
Александр Болдырев

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

cve github объекты prototype pollution прототип библиотека javascript setBy Path sca sast
Alt text

Ваш провайдер знает о вас больше, чем ваша девушка?

Присоединяйтесь и узнайте, как это остановить!