flawopen.com/Prototype Pollution/Object.assign()
Защищает ли Object.assign() от Prototype Pollution?
Не полностью. Object.assign() копирует собственные перечисляемые свойства, присваивая их целевому объекту. Если у источника есть собственный перечисляемый ключ __proto__, присваивание может вызвать унаследованный setter и изменить прототип целевого объекта. Одно поверхностное присваивание само по себе не меняет общий Object.prototype; рекурсивное слияние может привести к более широкому воздействию.
// A JSON own "__proto__" key reaches the inherited setter const source = JSON.parse('{"__proto__":{"isAdmin":true}}'); const target = Object.assign({}, source); // target's prototype changes; Object.prototype does not. // Recursive merge can reach Object.prototype function deepAssign(target, source) { for (const key of Object.keys(source)) { if (source[key] && typeof source[key] === 'object') { target[key] = target[key] || {}; deepAssign(target[key], source[key]); // With key === '__proto__', target[key] can be // Object.prototype; recursion then writes into it. } else { Object.assign(target, { [key]: source[key] }); } } }
// Spread creates an own data property; it does not invoke the setter. const shallow = { ...userInput }; // Reject dangerous keys at every depth and use own data properties. const blocked = new Set(['__proto__', 'constructor', 'prototype']); function isRecord(value) { if (value === null || typeof value !== 'object' || Array.isArray(value)) return false; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } function copySafe(value) { if (Array.isArray(value)) return value.map(copySafe); return isRecord(value) ? mergeSafe(value) : value; } function mergeSafe(source, target = Object.create(null)) { if (!isRecord(source)) throw new TypeError('Expected a plain object'); for (const key of Object.keys(source)) { if (blocked.has(key)) continue; const value = source[key]; const current = Object.hasOwn(target, key) ? target[key] : undefined; const next = isRecord(value) ? mergeSafe(value, isRecord(current) ? current : Object.create(null)) : copySafe(value); Object.defineProperty(target, key, { value: next, enumerable: true, writable: true, configurable: true }); } return target; }
Что Object.assign() делает с ключом __proto__
Object.assign() копирует каждое собственное перечисляемое свойство источника обычным присваиванием в цель. Если источник из JSON имеет собственное свойство __proto__, а цель наследует устаревший setter, он меняет [[Prototype]] цели. Собственное буквальное свойство __proto__ в этом случае не создаётся.
Object spread ({ ...source }) создаёт свойства-данные в результате: такой ключ становится собственным свойством-данными и не вызывает унаследованный setter. Это полезное отличие для поверхностной копии, но не общая защита от последующего рекурсивного слияния.
Как рекурсивное слияние загрязняет общие прототипы
Prototype Pollution возникает, когда небезопасный рекурсивный код следует по ключам под контролем атакующего и записывает данные в общий прототип. Типичный шаблон читает target[key], чтобы найти вложенный объект. Если key равен __proto__, чтение может вернуть Object.prototype; следующая рекурсия тогда повлияет на обычные объекты глобально. Нужно учитывать и пути вроде constructor.prototype.
При проверке найдите чтение и запись в целевой объект по ключам атакующего, выясните, вызывают ли они setter, и убедитесь, что каждый уровень рекурсии отклоняет опасные ключи.
Вопросы и ответы
{ ...obj })?Да для поверхностной копии: spread создаёт собственное свойство-данные и не вызывает унаследованный setter. Object.assign(), напротив, может изменить прототип цели при перечисляемом ключе источника __proto__. Ни одна поверхностная операция сама по себе не меняет общий Object.prototype.
structuredClone() создаёт отдельную копию и не применяет цепочку прототипов источника. Собственный ключ-данные __proto__ остаётся данными в копии; последующее небезопасное слияние всё ещё может интерпретировать его особым образом.
Используйте поддерживаемую и обновлённую библиотеку слияния; проверьте обработку __proto__, constructor и prototype на каждом уровне. Для ряда популярных merge и object-path утилит публиковались предупреждения о Prototype Pollution; исправления есть только в обновлённых версиях.
Источники
- MDN — Object.assign(): setter целевого объекта может быть вызван
- Спецификация ECMAScript — Object.assign
- MDN — Prototype Pollution в JavaScript
- CWE-1321 — Prototype Pollution
- Что такое Prototype Pollution?
- Защищён ли lodash.merge() от Prototype Pollution?
- Вызывает ли JSON.parse() загрязнение прототипа?