Dirty JavaScript — Prototype Pollution
Сегодня я расскажу про такую уязвимость, как загрязнение прототипа. Мы ознакомимся с практическим примером применения этой уязвимости в лабораторной, а также рассмотрим некоторые утилиты для её поиска.
Перед тем, как мы приступим к поиску уязвимостей загрязнения прототипа, очень важно разобраться, как работает данная уязвимость и причём тут объекты JavaScript.
В идеале вам необходимо понимать азы языка JavaScript, так как без знаний языка довольно трудно понять, почему данная уязвимость работает именно так.
Но даже если у вас недостаточно знаний самого языка, я постараюсь дать наиболее понятное представление о Prototype Pollution.
Теория
Для начала зайдём в консоль нашего браузера, в которой можно исполнить любой JS, и познакомимся с тем, что такое объект.
Каждый объект в JS имеет по умолчанию прототип, который наследуется от другого прототипа, общего для всех объектов в JS.
Объект в JS представляет собой комбинацию ключа и значения. Это очень похоже на словари в Python или какой-нибудь JSON.
Инициализируем переменную user:
var user = {username:"wr3dmast3r", password:"qwerty"}
Поскольку мы просто сохранили значения этой переменной в ключ-значениях, мы можем обратиться к ним с помощью конструкции <object>.<property>, например:
user.username
Таким образом мы получаем доступ к конкретному свойству объекта.
Создадим еще одну переменную и добавим к ней свойство isAdmin:
var admin = {username:"admin",password:"admin",isAdmin:true}
Как можно заметить, теперь можно обратиться к полю isAdmin и убедиться, что пользователь является администратором:
Создадим ещё одного пользователя:
var user = {username:"cherepawwka",password:"qweqwe"}
Теперь у нас есть два объекта, в одном из которых есть дополнительный параметр для администратора, который имеет значение true.
Предположим, что есть приложение, которое на самом деле проверяет какой пользователь имеет права администратора проверяя конкретное значение isAdmin.
В случае обращения к параметру isAdmin у обычного пользователя мы получаем неопределенное значение:
Теперь можно перейти к загрязнению прототипа, и начнем мы с того, что осознаем, что данная уязвимость возникает только тогда, когда злоумышленник может изменить или модифицировать значение своего прототипа.
Мы видим, что в данном объекте существует только два параметра, и пользователь не является администратором
У администратора в данном случае существует дополнительный параметр:
proto
Загрязним прототип объекта user с помощью __proto__
:
user.__proto__.isAdmin = true
Как мы видим, в ответе фигурирует true, но если мы снова посмотрим на объект user, то заметим, что ничего не изменилось:
Но если мы обратимся к свойству isAdmin данного объекта напрямую, то увидим наше переданное ранее значение:
И если раньше мы получали при таком запросе неопределенное значение, то теперь мы видим, что параметр имеет значение true. Это происходит потому, что мы добавили значение true свойства isAdmin в прототип, а когда вы добавляете свойство в прототип объекта JS, для другого объекта такого же класса, у которого это свойство не определено пользователем, JS будет пытаться найти его в свойстве __proto__
.
Если мы подробнее посмотрим на объект user, то увидим, откуда берется параметр isAdmin для этого пользователя:
На скриншоте вы можете увидеть, что у нас есть дополнительное свойство, которое мы добавили внутрь прототипа.
Сначала проверяется набор свойств, которые были заданы у объекта при инициализации. Если искомого свойства там нет, то JS будет заглядывать внутрь свойство прототипа.
В случае, если на сервере будет примерно такая логика, и будет существовать параметр, который мы сможем контролировать, то у нас появится возможность загрязнить прототип и авторизоваться как администратор.
Именно поэтому данная уязвимость называется "загрязнение прототипа", потому что мы модифицируем или искажаем значение предопределенных прототипов.
Практика
Перейдем к практике и рассмотрим уязвимое приложение. На главной странице в лабораторной существуют несколько разделов — контакты, главная страница и продукты.
Можно заметить, что каждая из страниц открывается под уникальным идентификаторов в параметре page:
Первое, на что стоит обратить внимание при поиске уязвимости, — это JS-скрипты, упомянутые в коде страницы.
Если мы проверим xss 0.3.3, то обнаружим, что существует известная уязвимость, которая приводит к DoS, что нам не подходит:
Для jquery 3.5.1 есть потенциальный эксплойт в репозитории @blackfan. В нем можно найти много полезной информации о Prototype Pollution. Если вдруг вы когда-нибудь столкнетесь с этой уязвимостью, данный репозиторий— первое, что необходимо посмотреть:
Если посмотреть на уязвимости jQuery.query версии 2.2.3, то можно найти упоминание уязвимости, связанной с Prototype Pollution:
Имеющиеся компоненты не предоставляют возможности для эксплуатации уязвимости, поэтому стоит осмотреться ещё раз и обратить внимание на упущенные моменты.
При подробном изучении главной страницы, можно найти кастомный скрипт, который используется для отрисовки элементов:
Скрипт начинается с создания страниц объекта, а в переменной pages находятся 4 страницы с какой-то HTML-разметкой. Дальше в коде можно увидеть создание переменной pl, которая берет из GET-запроса значение page. Затем идет проверка существования этой страницы, и, если она существуют, то выдается соответствующее HTML-содержимое, иначе нас перенаправляет на первую страницу из списка ("?page=1").
Если вы раньше изучали DOM XSS, то одна из вещей, на которую стоит обратить внимание, — это использование innerHTML, которое потенциально может привести к уязвимости.
Посмотрим на код внимательнее.
1. Если мы вводим цифру 4, то нам выдается статический HTML-код, который представляет собой верхнюю панель на странице.
2. Вторым параметром является pages[pl], где pl — это GET-параметр, то есть тот ввод, который мы контролируем.
Но даже если бы мы смогли что-то ввести в данный JS, добавленный фильтр помешал бы эксплуатации XSS.
Мы можем попробовать добавить новую страницу или изменить уже существующую, чтобы разместить свою полезную нагрузку.
Exploit
Попробуем загрязнить прототип с помощью полезной нагрузки и вызовем страницу:
__proto__[5]=payload&page=5
Добавим полезную нагрузку для исполнения уязвимости:
__proto__[5]=<img+src%3dx+onerror%3dalert(document.domain)>&page=5
Полезная нагрузка обрезается из-за фильтрации, последним шагом остается обойти её:
На этом этапе стоит вспомнить о ещё одном полезном свойстве прототипов — гаджетах, которые можно найти в разных библиотеках.
Передаю ещё раз привет моему кумиру @blackfan, и обращаюсь к его репозиторию
В описанном способе должно соблюдаться условие (должна быть установлена определенная настройка):
options.whiteList = options.whiteList || DEFAULT.whiteList;
Мы точно не знаем, какая настройка установлена в нашем случае, но благодаря загрязнению прототипа мы можем определить эту настройку и воспользоваться ею в наших целях.
Модернизируем полезную нагрузку:
__proto__[5]=<img+src=x+onerror%3Dalert(document.domain)>&__proto__[whiteList][img][0]=onerror&page=5
Сталкиваемся с тем, что она не отрабатывает:
Методом проб и ошибок удалось найти рабочий тэг и обработчик событий:
__proto__[5]=<style+onload%3Dalert(document.domain)>&__proto__[whiteList][style][0]=onload&page=5
И таким образом удается обойти валидацию и исполнить скрипт:
Инструменты
В основном при поиске уязвимостей я редко прибегаю к помощи сканеров, но есть несколько утилит, которым я отдаю предпочтение, когда захожу в тупик.
DOM Invader
Расширение практически сразу обнаруживает уязвимость в лабораторной, и от предложенного вектора можно развивать атаку:
Prototype Pollution Scanner
Полезным может быть также утилита pphack, принцип её работы, такой же как и у любого другого сканера, и вы сможете попробовать самостоятельно посмотреть его на практике.
github BlackFan
И не забываем про отличный репозиторий, с которым я настоятельно рекомендую ознакомиться при эксплуатации Prototype Pollution. На Payloads all the Things вы этого не найдете :)