Загрязненный — значит опасный: про уязвимость 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

Устали от того, что Интернет знает о вас все?

Присоединяйтесь к нам и станьте невидимыми!