Недавно я столкнулся с серией страшных сообщений в одном репозитории GitHub. Временная зависимость, казалось, вышла из-под контроля. Исправление было достаточно быстрым. Взорвать старые модули, поставить новые, но я хотел немного узнать о том, почему я внезапно проходил через этот процесс. Что заставило основы Github колебаться и дрожать?

Немного общения по моим каналам выявило виновника. Старая неприятная черта поведения, называемая загрязнением прототипов. Легко пропускаемая, но потенциально разрушительная ошибка, эксклюзивная для JavaScript.

Это было большое и страшное, и я хотел написать об этом…

Что такое прототип загрязнения?

Чтобы ответить на поставленный выше вопрос, нам нужно вернуться назад и немного напомнить себе о том, как работает JavaScript.

Непосвященный прототип лежит в основе того, как JavaScript обрабатывает наследование. Вместо указания иерархии поведения из родительских классов. Объекты JS имеют свойство по умолчанию, называемое «прототип», которое можно использовать для цепочки поведения. Если свойство вызывается для объекта и не найдено, язык проверяет свойство прототипа. Это особый атрибут всех объектов JS, который часто используется для установки поведения, которое должно наследоваться от других классов.

Загрязнение прототипа — это когда злоумышленник (хакер, если вы хотите получить все 90) использует какую-то неприятную инъекцию скрипта, чтобы перезаписать свойства прототипа объектов javascript, тем самым изменяя поведение набора объектов.

Что это значит?

Чтобы правильно это проиллюстрировать, давайте рассмотрим брешь в безопасности, о которой предупреждал GitHub. Ниже вы увидите пример функции Lodash defaultDeep…

_.defaultDeep( {‘Dante’ : { ‘brother’: ‘Vergil’}} , {‘Dante’ : { ‘brother’: ‘No one’, ‘son’ : ‘Nero’}} );

Результатом этого вызова будет:

{‘Dante’ : { ‘brother’: Vergil, ‘son’ : ‘Nero’}} 

defaultDeep — это средство установки свойств по умолчанию для объекта. Первый параметр — это объект, который должен быть defaultDeep(ed), а следующий — схема атрибутов по умолчанию, которые нужно заполнить, если они отсутствуют в первом объекте.

«Ну и что?» Я слышу, как ты плачешь! Стандартный ответ большинства разработчиков. Тем не менее, см. пример ниже….

_.defaultDeep({ ‘__proto__’ : { ‘toString’: ‘115’ } }, {‘Dante’ : { ‘brother’: ‘none’, ‘son’ : ‘Nero’}} )

Для тех, кто не в курсе, __proto__ — это метод получения прототипа объекта. Установив его в качестве первого параметра для defaultsDeep, вы будете активно перезаписывать прототип объекта. В свою очередь, изменение поведения экземпляров объекта на основе установленных значений по умолчанию.

"О нет!" Я слышу, как ты плачешь. По крайней мере, так я сделал, когда увидел это в действии.

Прототип загрязнения существует уже некоторое время, и почти всегда есть средства защитить себя, если возникнет такая необходимость. Хорошим местом для начала является метод Object.freeze(). Что явно предотвращает изменение объекта. Вы можете прочитать об этой технике здесь

Я Дрю. Это был какой-то код. Удачного дня