![]() |
https://forum.antichat.xyz/attachmen...5432563235.png
Prototype Pollution редко выглядит как громкая уязвимость. Обычно всё начинается с куска кода, который никто не считает опасным: функция слияния объектов, разбор параметров в структуру настроек, универсальный helper для deep copy, пустой config-объект, который потом уходит в рендер, загрузчик скрипта, шаблонизатор или системный вызов. До первого инцидента это воспринимается как нормальная инженерная обвязка. Проблема в том, что ошибка живёт не в одном конкретном месте. Один фрагмент принимает данные, другой пишет по ключу, третий позже читает унаследованное свойство как штатный параметр. В итоге баг возникает не на уровне одной функции, а на стыке нескольких безобидных решений. Этим Prototype Pollution и неприятна. По отдельности JSON.parse(), Object.assign(), разбор location.hash, рендер компонента или вызов child_process могут выглядеть совершенно спокойно. Но если пользовательское значение однажды доехало до прототипа, приложение начинает работать с чужими свойствами как со своими. На клиенте это выливается в DOM XSS, подмену src, srcdoc, обработчиков и конфигурации виджетов. На сервере - в подмену параметров фреймворка, шаблонизатора, промежуточного обработчика или системного API. Именно поэтому Prototype Pollution плохо ловится интуицией: источник находится в одном слое, эффект проявляется в другом, а между ними часто лежит обычный служебный код, который годами не вызывал подозрений. Эта уязвимость редко живёт как один баг в одном месте. Обычно это цепочка из трёх частей:
https://forum.antichat.xyz/attachments/4951404/1.png Где ломается модель объектов Почему пустой объект не пустой У обычного объекта в JavaScript есть собственные поля и прототип. Когда код читает свойство, движок сначала ищет его в самом объекте, потом поднимается по цепочке прототипов. Поэтому пустой {} на деле не совсем пустой - он наследует поведение от Object.prototype. Это нормальная механика языка. Проблема начинается, когда приложение даёт записать данные не в сам объект, а в его прототип. Тогда новые объекты начинают наследовать уже не только штатные свойства, но и то, что туда кто-то подложил. Если загрязнён Object.prototype, эффект расходится по большому куску приложения сразу. Если меняется прототип конкретного объекта настроек, масштаб меньше, но для эксплуатации этого часто уже хватает. Уязвимости не обязательно нужно испортить весь рантайм. Иногда достаточно сломать один объект, который потом уйдёт в чувствительный код. proto, prototype и constructor.prototype На этой теме часто спотыкаются даже те, кто уже сталкивался с Prototype Pollution. Код:
__proto__prototype - свойство функции-конструктора, из которого создаются прототипы экземпляров. constructor.prototype - обходной путь к тому же объекту. https://forum.antichat.xyz/attachments/4951404/2.png Откуда берётся Prototype Pollution Небезопасное рекурсивное слияние Самый частый источник - самописное глубокое слияние объектов. Код выглядит настолько обычным, что его почти не замечают на ревью. JavaScript: Код:
functionJavaScript: Код:
constЭто и есть одна из причин, почему баг живёт так долго. Разработчик смотрит на JSON.parse() - там всё спокойно. Смотрит на deepMerge() - тоже вроде без криминала. Проблема рождается на стыке. Параметры URL и разбор пути Во фронтенде источник pollution часто сидит в коде, который вообще не воспринимается как опасный. Например, страница умеет превращать query string во вложенный объект настроек. JavaScript: Код:
functionНа ревью такие места проще узнавать по трём признакам сразу:
Цитата:
Во фронтенде опасны не только HTML-вставки. Нередко загрязнение доезжает до загрузки скриптов. JavaScript: Код:
functionJavaScript: Код:
constОбработчики событий и поля обратного вызова Есть ещё один класс гаджетов, который часто упускают. Приложение не пишет в DOM напрямую, но использует значение из объекта настроек как функцию, имя обработчика, строку для таймера или параметр инициализации сторонней библиотеки. Можно так: JavaScript: Код:
functionJavaScript: Код:
functionКак это выглядит в коде SPA Если свести типовой клиентский сценарий к минимуму, получится что-то такое: JavaScript: Код:
function
Server-side: где начинается опасная часть На сервере Prototype Pollution неприятнее по двум причинам. Во-первых, загрязнение живёт в процессе до перезапуска. Во-вторых, даже аккуратная проверка может случайно превратиться в отказ в обслуживании, если задеть не тот путь и не тот гаджет. Поэтому для серверной части полезно начинать не с попыток сразу добраться до выполнения команд, а с безопасных индикаторов - таких, которые меняют наблюдаемое поведение сервиса, но не ломают его. Безопасный индикатор на Express Ниже минимальный пример, где pollution сначала попадает в процесс, а потом проявляется в другом обработчике. JavaScript: Код:
constЭто очень полезный практический кусок по двум причинам. Во-первых, он показывает server-side pollution без опасной операционки. Во-вторых, сразу видно главное отличие от клиентской части: загрязнение не исчезает после одного рендера. Оно остаётся жить в процессе и начинает влиять на последующие запросы. Почему child_process становится гаджетом Теперь можно переходить к серверным эффектам посерьёзнее. Опасность child_process не в том, что разработчик обязательно запускает внешнюю команду с пользовательской строкой. Для Prototype Pollution это даже не нужно. Проблема начинается там, где код передаёт в API запуска процесса пустой или неполный объект опций и рассчитывает, что недостающие поля просто останутся не заданными. JavaScript: Код:
constШаблонизаторы и скрытая конфигурация С шаблонизаторами история похожая. Проблема обычно не в том, что приложению напрямую подсовывают произвольный шаблон. Проблема в том, что в render(), compile() или renderFile() передаётся объект настроек, часть которого должна была быть пустой или безопасной по умолчанию. JavaScript: Код:
functionОсобенно больно это находить в больших Node.js-приложениях, где шаблонизатор обёрнут несколькими внутренними слоями, а источник pollution сидит вообще в другой части сервиса. Цитата:
Для клиентской части хорошо работают инструменты, которые умеют быстро находить источник pollution и проверять, доезжает ли значение до опасного места в DOM. Здесь особенно полезны DOM Invader и похожие средства для анализа клиентских цепочек. Для первичного скрининга встречаются и более узкие утилиты вроде ppmap. Для серверной части автоматизация полезна, но не стоит ждать от неё чуда. Наружный скан может подсветить безопасные индикаторы - странный код ответа, поведение JSON, неожиданные заголовки. Настоящая ценность обычно появляется при разборе зависимостей, обвязок над child_process, шаблонизаторов и функций слияния данных. Инструменты здесь экономят время, но не заменяют понимание формы бага. Prototype Pollution слишком часто живёт в стыках между слоями, чтобы её можно было полностью отдать одному сканеру. Как чинить это без косметики https://forum.antichat.xyz/attachments/4951404/3.png Не делать “универсальное” глубокое слияние пользовательских объектов Первое и самое полезное исправление - перестать бездумно вливать произвольный пользовательский объект в структуру приложения. Вместо “умного” общего deepMerge() почти всегда лучше работает явное извлечение разрешённых полей. JavaScript: Код:
functionНе использовать обычные объекты как словари Если нужна структура “ключ-значение” с пользовательскими ключами, лучше брать Map или объект без прототипа. JavaScript: Код:
constJavaScript: Код:
constПроверять принадлежность поля самому объекту Если приложение читает необязательные свойства у объекта настроек, полезно проверять, что поле реально принадлежит самому объекту, а не прототипу. JavaScript: Код:
functionПочему заморозка прототипа не поможет Идея с Object.freeze(Object.prototype) регулярно всплывает как быстрое средство защиты. Как дополнительный барьер она полезна, но не решает архитектурную проблему сама по себе. У неё есть цена: совместимость, порядок инициализации, поведение полифилов, неожиданные побочные эффекты в старом коде. Если приложение уже массово опирается на универсальные merge helper’ы, пустые объекты настроек и динамическую запись по путям, заморозка прототипа будет скорее аварийным тормозом, чем нормальным исправлением. Вместо заключения Как только в приложении есть путь записи в прототип и участок кода, который читает несуществующее свойство как допустимое значение по умолчанию, дальше уже начинается не “теоретическая особенность JavaScript”, а вполне рабочая поверхность атаки. На клиенте она уходит в DOM XSS и подмену загрузки. На сервере - в управление поведением процесса, фреймворка и системных вызовов. Хорошая защита начинается не с поиска одного запретного ключа. Она начинается там, где команда перестаёт воспринимать универсальный объект как безопасный контейнер для всего подряд. |
Слушай, идея с deepMerge часто выглядит круто, но реально грязно — особенно когда по прототипу кто-то умудряется затащить данные. Это не баг одной функции, а системный косяк, который долго не всплывает, пока не бахнет где-то в другом месте. Особенно странно, что в коде всё вроде нормально, а на деле всё летит из-за такого мутного слияния. Лучше действительно контролировать поля явно, иначе потом эти сюрпризы очень больно бьют.
|
Проблема с prototype pollution реально кроется в том, что вроде бы обычный код с deepMerge превращается в ловушку — данные из запроса внезапно вливаются в прототип и меняют поведение всего приложения. Особенно прикольно, когда баг проявляется не сразу, а в нескольких местах по цепочке. Поэтому лучше явно указывать, какие поля мутировать, а не впускать всё подряд. И Object.create(null) — спасение от таких грязных сюрпризов.
|
Prototype Pollution — это классика, когда обычное слияние объектов внезапно превращается в бомбу замедленного действия. Вроде код простой и понятный, а потом где-то в другом месте результат проявляется. Главная беда — чтение свойств из прототипа вместо самого объекта, особенно если это настройки, которые потом влияют на поведение всего приложения. Поэтому лучше лишний раз чётко фильтровать поля и не таскать пользовательские данные в прототип.
|
Короче, Prototype Pollution — это когда непреднамеренно ковыряешь прототип объекта через глубокое слияние, и вся фигня начинает гулять по приложению. Особенно опасно, когда данные из клиента вливаются в конфиги и потом ломают рендер или серверное поведение. Главное — избегать универсальных deepMerge без фильтрации и не пихать пользовательские данные прямо в прототип. Вот и вся петрушка.
|
В целом, ошибка с prototype pollution получается из-за того, что глубокое слияние на входе может непреднамеренно затронуть прототипы объектов. Это ломает логику, потому что все новые объекты начинают наследовать чужие свойства — и баг часто проявляется далеко от места записи. Главное — избегать слепого мерджа и фильтровать данные, а ещё полезно использовать объекты без прототипа или явно проверять свои поля.
|
Ахах, prototype pollution – это как вирус в бутылке с водой: вроде прозрачная штука, а потом внутри тебя уже мутирует фигня из прототипа. Особенно когда deepMerge без мозгов ставят, будто это волшебная палочка. Главное – не давать клиентским данным дико разгуляться по прототипам, а то получишь глобальный сюрприз, который проявится в самых неожиданных местах. Это всё не баг, а фича для тех, кто любит приключения!
|
| Время: 08:16 |