FlawOpen

flawopen.com/Загрязнение прототипа/JSON.parse()

Вызывает ли JSON.parse() загрязнение прототипа?

CWE-1321: Загрязнение прототипаСправочник
Краткий ответ

Нет. JSON.parse() создаёт собственное свойство данных с именем __proto__; разбор не вызывает унаследованный setter и не меняет прототип. Риск возникает позже, если код копирует этот ключ через setter, рекурсивно объединяет недоверенные ключи или записывает их по контролируемому атакующим пути объекта.

НЕБЕЗОПАСНОЕ КОПИРОВАНИЕ — ЛОКАЛЬНАЯ ЦЕЛЬ
// JSON.parse создаёт данные, но не загрязняет прототип.
const input = '{"__proto__":{"isAdmin":true}}';
const parsed = JSON.parse(input);

// Object.assign использует Set для цели. Здесь меняется только
// прототип этого локального демонстрационного объекта.
const copied = Object.assign({}, parsed);

Object.getPrototypeOf(copied).isAdmin; // true
Object.prototype.isAdmin;              // undefined
БЕЗОПАСНАЯ ОБРАБОТКА — ИЗВЕСТНАЯ ФОРМА
// Разберите данные и создайте только разрешённые поля.
const input = '{"theme":"dark","locale":"en","__proto__":{"isAdmin":true}}';
const parsed = JSON.parse(input);
const config = {
  theme:  parsed.theme,
  locale: parsed.locale,
};

// Для недоверенных ключей словаря избегайте унаследованных setter.
const options = Object.assign(Object.create(null), parsed);

Почему JSON.parse() не является источником загрязнения

Ключ JSON и setter прототипа в литерале объекта — разные операции. Разбор JSON определяет собственные свойства нового значения и не выполняет obj.__proto__ = value. Последующие операции могут работать иначе: Object.assign(target, source) присваивает свойства цели, поэтому собственный ключ __proto__ может вызвать унаследованный setter цели. Это меняет прототип целевого объекта, но само по себе не изменяет Object.prototype.

После const o = JSON.parse('{"__proto__":{"a":1}}') выражение Object.hasOwn(o, "__proto__") истинно, а Object.prototype.a остаётся неопределённым. Последующее копирование этого ключа в обычную цель может изменить её прототип. Spread объектов создаёт собственные свойства и не вызывает setter, но не защищает от небезопасного рекурсивного слияния или записи по пути.

Где возникает реальный риск

Разобранный JSON — недоверенные данные, а не загрязнённый прототип. Риск зависит от последующих операций записи: рекурсивного merge, setter пути или цикла копирования с присваиванием свойств обычному объекту. Уязвимое рекурсивное слияние может пройти через __proto__ или constructor.prototype к общему прототипу; копирование одного уровня может изменить только цель. Перед оценкой воздействия изучите конкретную функцию и объект назначения.

Проверяйте места записи недоверенных ключей, а не только вызовы JSON-парсера: глубокие merge-функции, setter путей и циклы for...in с присваиванием. Отклоняйте __proto__, constructor и prototype, если ключами управляет пользователь; предпочитайте проверенную схему известной формы или словарь без прототипа.

Частые вопросы

Всегда ли нужно удалять __proto__ через reviver?

Reviver может удалить этот ключ при разборе, если приложению он не нужен. Это дополнительная защита, но она не заменяет исправление небезопасных слияний и setter путей; важны также constructor.prototype и источники не из JSON.

Защищают ли меня Express или body-parser?

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

Представляет ли JSON.stringify() риск загрязнения прототипа?

Нет. Сериализация читает значения и создаёт текст JSON; она не меняет прототип. Риск может появиться снова, когда этот текст разбирают и передают небезопасной функции.

Источники

См. также Загрязнение прототипа JavaScript: причины и защита JSON.parse() безопаснее, чем eval()? Защищён ли lodash.merge() от загрязнения прототипа? Object.assign() и Prototype Pollution: что безопасно?