Prototype pollution — доволь­но извес­тная ата­ка в таких язы­ках, как JavaScript или Python, которая при­водит к серь­езным пос­ледс­тви­ям. Иссле­дуя один популяр­ный про­ект, я при­думал, как мож­но экс­плу­ати­ровать про­тоти­пы по‑новому, и решил поделить­ся этой иде­ей.

В этой статье я рас­ска­жу, как работа­ют про­тоти­пы, что такое 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);
// true
console.log(pepperoni.sausage);
// true

Ко­неч­но, ребенок необя­затель­но дол­жен нас­ледовать все свой­ства родите­ля, а может переза­писать некото­рые из них.

// Маринара тоже наследует основу,
// но переопределяет cheese
class Marinara extends Pizza {
cheese = false;
}
const marinara = new Marinara();
console.log(pepperoni.cheese);
// true
console.log(marinara.cheese);
// false

Так­же мож­но соз­давать цепоч­ки нас­ледова­ния, нап­ример:

// Веганская пеперони наследуется от Pepperoni
class VeganPepperoni extends Pepperoni {
sausage = false;
veganSausage = true;
}
const veganPepperoni = new VeganPepperoni();
console.log(veganPepperoni.veganSausage);
// true
console.log(veganPepperoni.sausage);
// false
console.log(veganPepperoni.cheese);
// true

Каж­дый объ­ект име­ет внут­реннее свой­ство __proto__ — ссыл­ку на про­тотип. Ког­да мы пыта­емся получить какое‑то свой­ство объ­екта, сна­чала оно ищет­ся в самом объ­екте, а потом с помощью __proto__ — в про­тоти­пе.

Ес­ли нет в про­тоти­пе, то в про­тоти­пе про­тоти­па. Нап­ример, вот как будет выг­лядеть поиск свой­ства cheese в VeganPepperoni из при­мера.

Есть еще два полез­ных свой­ства, свя­зан­ных с про­тоти­пом: constructor и prototype. Constructor — свой­ство про­тоти­па, которое ука­зыва­ет на фун­кцию, соз­давшую этот про­тотип. Prototype — это, наобо­рот, свой­ство конс­трук­тора, которое ука­зыва­ет на про­тотип, с которым будет соз­дан объ­ект.

console.log([].__proto__ === Array.prototype)
// true
console.log([].constructor === Array)
// true

В JavaScript поч­ти всё — объ­екты и поч­ти у всех объ­ектов есть про­тотип. Нап­ример, у строк есть про­тотип, который пре­дос­тавля­ет спе­цифич­ные для строк методы: charAt, search и так далее. У это­го про­тоти­па тоже есть про­тотип — Object.prototype, который добав­ляет нес­коль­ко базовых методов. Так как в JS поч­ти всё явля­ется объ­екта­ми, так или ина­че любой объ­ект нас­леду­ется от объ­екта Object.

console.log(''.__proto__ == String.prototype) // true
console.log(''.__proto__.__proto__ == Object.prototype) // true

Он же и пред­став­ляет собой исклю­чение: его про­тотип — null, и null — единс­твен­ная сущ­ность, которая не явля­ется объ­ектом и у которой нет про­тоти­па.

console.log({}.__proto__.__proto__) // null
console.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) // true
console.log(marinara.pineapple) // true
console.log(veganPepperoni.pipeapple) // true
console.log({}.pineapple) // true

Пос­мотрим, что про­исхо­дит с pepperoni из при­мера: сна­чала свой­ство pineapple про­веря­ется в самом объ­екте — его там нет. Перехо­дим к про­тоти­пу — это pizza, и там это­го свой­ства тоже нет (есть толь­ко cheese). А сле­дующий про­тотип — общий для всех объ­ектов. Его получи­лось заг­рязнить, и там это свой­ство есть.

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

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

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии