Загрязнение прототипов — известная уязвимость в JavaScript и Python, которая может привести к серьёзным последствиям в приложениях. В этом руководстве мы разберёмся, как работает система прототипного наследования, что такое атака на прототипы и как безопасно использовать прототипы в своем коде.
Статья охватывает основные концепции прототипов JavaScript, объясняет суть проблемы загрязнения прототипов и предлагает практические решения. Хотя примеры написаны на JavaScript, многие принципы применимы и к другим языкам программирования, использующим прототипное наследование.
Понимание прототипов в JavaScript
Прототип — это фундаментальный механизм JavaScript, позволяющий объектам наследовать свойства и методы от других объектов. Каждый объект содержит скрытое свойство __proto__, которое ссылается на объект-прототип, от которого он наследует характеристики.
Рассмотрим пример иерархии классов. Базовый класс определяет основные свойства, а производные классы расширяют эту функциональность, добавляя специфичные для них характеристики. Если переопределить свойство в производном классе, оно переопределяет значение из базового класса.
Иерархия может быть многоуровневой. Например, класс может наследовать от другого класса, который в свою очередь наследует от третьего, создавая цепочку наследования.
Когда приложение ищет свойство объекта, оно сначала проверяет сам объект. Если свойство не найдено, поиск переходит вверх по цепочке прототипов, используя свойство __proto__, пока свойство не будет найдено или цепочка не закончится.
С прототипами связаны два важных свойства. Свойство constructor указывает на функцию-конструктор, создавшую данный прототип. Свойство prototype конструктора указывает на сам прототип, который будет использоваться при создании новых объектов.
В JavaScript практически всё является объектом, и почти каждый объект имеет прототип. Строки, массивы, функции — все они имеют соответствующие прототипы с методами, специфичными для своего типа. На вершине цепочки наследования находится Object.prototype, который предоставляет базовые методы для всех объектов.
Единственное исключение — значение null, которое не является объектом и не имеет прототипа.
Уязвимость загрязнения прототипов
Основная проблема заключается в том, что несколько объектов могут совместно использовать один прототип. Если один объект имеет свойство __proto__, указывающее на определённый прототип, то другие объекты того же типа будут указывать на этот же прототип.
Это означает, что если добавить новое свойство в общий прототип, оно немедленно появится у всех объектов, которые этот прототип используют. Более того, такое свойство будет доступно даже объектам, созданным в будущем.
Опасность уязвимости состоит в том, что нежелательное свойство может быть добавлено в Object.prototype — универсальный прототип, который наследуется практически всеми объектами приложения. Когда поиск свойства проходит через всю цепочку наследования и не находит нужное свойство в конкретных объектах или их непосредственных прототипах, он обнаруживает загрязненное свойство в Object.prototype.
Эта уязвимость может быть использована для внедрения вредоносных значений, изменения поведения приложения или компрометации безопасности системы. Поэтому необходимо применять защитные меры при работе с объектами, полученными из ненадёжных источников, и избегать прямого манипулирования свойством __proto__.
