Prototype Pollution
Prototype Pollution — атака на JavaScript-приложения, при которой злоумышленник модифицирует
Object.prototype, добавляя свойства, которые наследуют все объекты в приложении, что может приводить к DoS, обходу проверок и RCE.
Зачем нужно
JavaScript использует прототипное наследование: каждый объект наследует свойства Object.prototype. Если атакующий может внедрить свойство в прототип через __proto__, constructor.prototype или Object.defineProperty, это затронет все объекты приложения.
Где используется
- Функции глубокого копирования объектов (
merge,clone,extend) - Парсинг пользовательских JSON-данных с
__proto__ключами - Template engines (Handlebars, Pug) — уязвимые версии
- Библиотеки работы с путями объектов (lodash
<4.17.12— исторический пример)
Основной контент
Пример атаки
// Уязвимая функция deep merge
function merge(target, source) {
for (const key of Object.keys(source)) {
if (typeof source[key] === 'object') {
merge(target[key], source[key]); // Не проверяем key!
} else {
target[key] = source[key];
}
}
}
// Атакующий передаёт JSON:
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, malicious);
// Теперь у ВСЕХ объектов есть isAdmin: true
const user = {};
console.log(user.isAdmin); // true — обход проверки прав!
Безопасный deep merge с проверкой ключей
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
// Запрещаем опасные ключи
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
safeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
Создание объекта без прототипа
// Object.create(null) создаёт объект без прототипа
const safeMap = Object.create(null);
safeMap['__proto__'] = 'harmless'; // Просто ключ, не прототип
// Использование Map вместо plain object
const map = new Map();
map.set('__proto__', 'harmless'); // Безопасно
JSON.parse + валидация
// JSON.parse не создаёт прototypechain, но Object.assign может перенести
const parsed = JSON.parse(input);
// Проверяем наличие опасных ключей
function hasProtoPollution(obj) {
return '__proto__' in obj || 'constructor' in obj;
}
if (hasProtoPollution(parsed)) {
throw new Error('Suspicious input');
}
Частые ошибки
- Использование уязвимых версий lodash (
.merge,.set,.defaultsDeep— до 4.17.12) - Рекурсивный merge объектов без проверки ключей
__proto__иconstructor - Доверие
JSON.parseбез проверки структуры на опасные ключи - Использование
hasOwnPropertyкак проверки в merge — не защищает от__proto__
Связанные темы
- _MOC Безопасность
- Insecure Deserialization
- Экранирование и санитизация ввода
- Supply Chain Attacks -- npm