Содержание статьи
В этой статье я расскажу, как работают прототипы, что такое prototype pollution и как еще можно эксплуатировать прототипы. Для примеров будем использовать JavaScript, но частично это применимо и к другим языкам на основе прототипов.
Что такое Prototype
Прототип — это механизм JavaScript, с помощью которого одни объекты наследуют свойства других. Для этого у объектов есть свойство __proto__, которое указывает на другой объект, чьи свойства они наследуют.
// Основа для пиццыclass Pizza { base = "thin"; cheese = true;}// Пеперони наследует основуclass Pepperoni extends Pizza { sausage = true;}const pepperoni = new Pepperoni();console.log(pepperoni.__proto__);// Pizza {}console.log(pepperoni.base);// "thin"console.log(pepperoni.cheese);// trueconsole.log(pepperoni.sausage);// trueКонечно, ребенок необязательно должен наследовать все свойства родителя, а может перезаписать некоторые из них.
// Маринара тоже наследует основу,// но переопределяет cheeseclass Marinara extends Pizza { cheese = false;}const marinara = new Marinara();console.log(pepperoni.cheese);// trueconsole.log(marinara.cheese);// falseТакже можно создавать цепочки наследования, например:
// Веганская пеперони наследуется от Pepperoniclass VeganPepperoni extends Pepperoni { sausage = false; veganSausage = true;}const veganPepperoni = new VeganPepperoni();console.log(veganPepperoni.veganSausage);// trueconsole.log(veganPepperoni.sausage);// falseconsole.log(veganPepperoni.cheese);// trueКаждый объект имеет внутреннее свойство __proto__ — ссылку на прототип. Когда мы пытаемся получить какое‑то свойство объекта, сначала оно ищется в самом объекте, а потом с помощью __proto__ — в прототипе.
Если нет в прототипе, то в прототипе прототипа. Например, вот как будет выглядеть поиск свойства cheese в VeganPepperoni из примера.

Есть еще два полезных свойства, связанных с прототипом: constructor и prototype. Constructor — свойство прототипа, которое указывает на функцию, создавшую этот прототип. Prototype — это, наоборот, свойство конструктора, которое указывает на прототип, с которым будет создан объект.
console.log([].__proto__ === Array.prototype)// trueconsole.log([].constructor === Array)// trueВ JavaScript почти всё — объекты и почти у всех объектов есть прототип. Например, у строк есть прототип, который предоставляет специфичные для строк методы: charAt, search и так далее. У этого прототипа тоже есть прототип — Object., который добавляет несколько базовых методов. Так как в JS почти всё является объектами, так или иначе любой объект наследуется от объекта Object.
console.log(''.__proto__ == String.prototype) // trueconsole.log(''.__proto__.__proto__ == Object.prototype) // trueОн же и представляет собой исключение: его прототип — null, и null — единственная сущность, которая не является объектом и у которой нет прототипа.
console.log({}.__proto__.__proto__) // nullconsole.log(null.__proto__)// Uncaught TypeError: Cannot read properties of null (reading '__proto__')Что может пойти не так, или что такое prototype pollution
Проблема тут в том, что, если два объекта были созданы с одним и тем же прототипом, он будет общим, то есть свойство __proto__ будет указывать на один и тот же объект.
Вот пример с пиццами:
console.log(pepperoni.__proto__ === marinara.__proto__)// Выведет trueБолее того, есть прототип, который есть вообще у всего, кроме null, — это прототип класса Object.
Уязвимость заключается в том, что, если каким‑то образом добавить свойство в прототип, оно сразу появится у всех объектов, у которых его нет, даже у тех, которые будут созданы позже.
// Загрязняем прототипconst someObject = {}// Добавили в прототип класса Object свойствоsomeObject.__proto__.pineapple = true// Теперь проверим наши пиццыconsole.log(pepperoni.pineapple) // trueconsole.log(marinara.pineapple) // trueconsole.log(veganPepperoni.pipeapple) // trueconsole.log({}.pineapple) // trueПосмотрим, что происходит с pepperoni из примера: сначала свойство pineapple проверяется в самом объекте — его там нет. Переходим к прототипу — это pizza, и там этого свойства тоже нет (есть только cheese). А следующий прототип — общий для всех объектов. Его получилось загрязнить, и там это свойство есть.

Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
