Перейти к содержимому
>_ITDITDПлатформа веб-безопасности

Руководства по безопасности

Загрязнение прототипа в Node.js: что не закрывает среда выполнения и как защитить свой код

Специальный ключ во входящих данных, пройдя через рекурсивное слияние объектов, приводит к тому, что у несвязанных объектов появляются свойства, которые вы не задавали. Node.js заявляет, что это не уязвимость ядра, поэтому защита — ваша ответственность.

Опубликовано 2026-09-05 Обновлено 2026-10-04 Последняя проверка 2026-09-05 7 мин чтения

Вы принимаете данные извне, а потом у несвязанных объектов появляются свойства, которые вы не задавали. Или стандартный метод вроде hasOwnProperty вдруг оказывается «not a function», и приложение падает. Оба случая — признаки загрязнения прототипа.

Что закрывает Node.js и что остаётся вам

Node.js прямо говорит, что это не его уязвимость

Официальное руководство Node.js по безопасности говорит однозначно: «Согласно модели угроз Node.js, загрязнение прототипа, основанное на контроле атакующего над пользовательским вводом, не считается уязвимостью ядра Node.js, потому что Node.js доверяет входным данным, которые передаёт код приложения».

И сразу после этого: «Тем не менее загрязнение прототипа — серьёзный класс уязвимостей для приложений на Node.js и сторонних библиотек, и защиту следует строить на уровне приложения и зависимостей».

Это не противоречие, а указание на то, кто отвечает. Среда выполнения не гарантирует, что входные данные корректны. Значит, «подождать CVE, подождать обновления» здесь не работает. Это ещё один случай того, с чем этот сайт сталкивается постоянно: за то, что, как вы думали, кто-то закрывает, на деле никто не отвечает.

Что ломается (два последствия)

Внешние данные (JSON, query, форма)

содержат специальный ключ

↓ рекурсивное слияние / глубокое копирование / сборка настроек

Общий шаблон (прототип) перезаписан

↓ а с ним и всё, что от него наследует

1. Появляются значения, которые вы не задавали

проверки флагов прав и настроек ломаются

2. Исчезают встроенные методы

приложение падает в несвязанном месте (DoS)

Запись, предназначенная одному объекту, затрагивает всех, кто использует общий шаблон. Поэтому ущерб проявляется в несвязанном месте.
Два последствия с точки зрения эксплуатации
1. Внедрение свойств
Значение, «которое никак не могло быть задано», становится читаемым. Везде, где объект настроек или флаг прав определяется простой проверкой наличия, внедрённому значению просто верят. Если оно доходит до решения об авторизации, это проблема прав; если до сборки шаблона или команды — может быть гораздо хуже
2. Отказ в обслуживании
Если сломать сам шаблон, встроенный метод вроде hasOwnProperty выдаёт ошибку «is not a function». Документация называет это возможным DoS. Главное, что стоит запомнить: дело не только в повышении привилегий
Насколько широко
Приводимые примеры — сам Node.js (CVE-2022-21824) и сторонняя библиотека (Lodash, CVE-2018-3721). То, что вы сами не писали рекурсивный merge, ничего не меняет, если его написали в зависимости

Меры защиты из документации (семь)

Node.js перечисляет их прямо. Их проще применять, если сгруппировать по назначению: остановить на входе, сделать структуру незагрязняемой и изменить способ чтения.

Остановить на входе

・Избегать небезопасного рекурсивного слияния (в документации приводится CVE-2018-16487)
・Применять проверку по JSON Schema к внешним и недоверенным запросам

Не пускать неожиданные ключи вообще — самый надёжный путь. Пересмотрите архитектуру, где глубокий объект принимается целиком и сливается с текущим состоянием.

Изменить структуру и чтение

・Object.create(null) — создавать объекты без прототипа (идеально для словарей)
・Object.freeze(MyObject.prototype) — заморозить прототип
・Флаг Node --disable-proto — отключить Object.prototype.proto
・Object.hasOwn(obj, key) — проверять, что свойство принадлежит самому объекту
・Не использовать методы из Object.prototype

Больше всего даёт смена способа проверки наличия

На практике самый широкий эффект даёт переход на Object.hasOwn(obj, key). Простая проверка наличия («есть ли такое свойство?») отвечает «да» и для значений, унаследованных от шаблона, — именно так внедрённое значение и подхватывается. Проверка того, что свойство принадлежит самому объекту, сама по себе предотвращает большую часть первого последствия.

По той же причине не вызывайте obj.hasOwnProperty(key) у самого объекта — этот метод как раз может быть удалён загрязнением (второе последствие).

Что проверить в своём коде

1

Принимаете ли вы внешние данные как глубокий объект?

Тела запросов, строки запроса, формы, данные вебхуков — найдите все места, где вложенная структура принимается целиком и сливается с текущим состоянием. Только там внешние данные и попадают внутрь. Самое короткое исправление — явно решить через схему, какие ключи вы принимаете.

2

Измените способ создания объектов-словарей

Всё, что используется как «контейнер, ключи которого определяются во время работы» (словарь по ID, набор настроек), создавайте через Object.create(null). Без шаблона наследовать нечего. Решите структурой то, что иначе пришлось бы решать проверками.

3

Проверяйте наличие свойств через Object.hasOwn

Найдите ветвления, зависящие от наличия свойства, и переведите их. Сначала проверки прав, флагов и настроек — они напрямую связаны с реализацией авторизации.

4

Поставьте зависимости под автоматический контроль

То, что вы сами не писали рекурсивный merge, ничего не меняет, если его написали в зависимости — поэтому среди примеров есть сторонняя библиотека. Известные уязвимости в зависимостях вручную не отследить, поэтому поставьте их под автоматический контроль, как в статье начало работы с osv-scanner. О более широком риске заражённой зависимости — в статье защита от атак на цепочку поставок npm.

5

Так же посмотрите на вход своего серверного фреймворка

У каждого фреймворка на Node есть путь, который десериализует тело запроса. Когда мы проверили данные NVD, уязвимости Next.js концентрировались в серверных задачах — кэшировании, middleware, Server Actions (много ли уязвимостей в Next.js и React?). Начните с отказа от предположения «это фронтенд-библиотека, значит, нас не касается».

Взгляд этого сайта: если граница ответственности указана явно, проверяйте сами

Самое полезное в этой статье для практики — не список мер, а фраза о том, что это не считается уязвимостью ядра Node.js. Когда ответственность обозначена так чётко, то, что за её пределами, никто не закрывает: CVE не будет, и обновление это не исправит.

Мы считаем это проблемой того же вида, что и сертификат, который не защищает от захвата (захват поддомена), и блокировка, которая не удаляет существующую политику под ней (публичные бакеты). Кто на самом деле отвечает за то место, которое вы считали закрытым? Если один раз ответить на этот вопрос, приоритеты обычно заметно меняются.

Источники (первичные)

  • Node.js, «Security Best Practices» (раздел Prototype Pollution Attacks / CWE-1321) — nodejs.org (позиция модели угроз, оба последствия, приводимые CVE и семь мер взяты из этого документа; проверено 5 сентября 2026)

Что прочитать дальше

FAQ

QЗагрязнение прототипа — это ошибка Node.js? Исправит ли её обновление?
A

Обновления не будет. Официальное руководство Node.js по безопасности указывает, что согласно модели угроз Node.js загрязнение прототипа, основанное на контроле атакующего над пользовательским вводом, не считается уязвимостью ядра Node.js, потому что Node.js доверяет входным данным, которые передаёт код приложения. Дальше сказано: тем не менее загрязнение прототипа — серьёзный класс уязвимостей для приложений на Node.js и сторонних библиотек, и защиту нужно строить на уровне приложения и зависимостей. По замыслу это ваша ответственность.

QЧто на самом деле ломается?
A

Два последствия. Первое: у объектов по всему процессу появляются свойства, которые вы не задавали; если флаг прав или настройка определяются простой проверкой наличия, внедрённому значению поверят, и авторизация или бизнес-логика пойдут не так. Второе — отказ в обслуживании: если сломать встроенный прототип, стандартный метод вроде hasOwnProperty выдаёт ошибку «is not a function», и падает код, никак не связанный с входными данными. Второе последствие показывает, что дело не только в повышении привилегий.

QГде это исправлять?
A

На входе и в структурах данных. На входе применяйте проверку по JSON Schema к внешним и недоверенным запросам, чтобы неожиданные ключи вообще не попадали внутрь. В структурах откажитесь от небезопасного рекурсивного слияния и создавайте объекты-словари через Object.create(null), чтобы у них вообще не было прототипа. При чтении проверяйте через Object.hasOwn(obj, key), что свойство принадлежит самому объекту, и не опирайтесь на методы Object.prototype. Для эксплуатации есть также Object.freeze и флаг Node --disable-proto.

QЯ никогда не писал рекурсивный merge — я в безопасности?
A

Нет. В документации приводятся примеры и из самого Node.js (CVE-2022-21824), и из сторонней библиотеки (Lodash, CVE-2018-3721). Слияние конфигураций, разбор строки запроса и десериализация форм — всё это места, где популярные библиотеки собирают глубокие объекты за вас. Поэтому защита не замыкается в вашем коде: к ней нужно добавить автоматическое отслеживание зависимостей.