У JavaScript об'єкти успадковують властивості від прототипу, і всі звичайні об'єкти мають спільний Object.prototype. Забруднення прототипу - зловмисник через дані змушує код записати властивість у сам прототип, і вона з'являється в усіх об'єктах застосунку.
Як це трапляється - рекурсивне злиття чи встановлення властивості за шляхом з даних користувача:
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
target[key] ??= {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
merge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
({}).isAdmin; // true - у кожного об'єкта
Ключ __proto__ вказує на прототип, тож запис іде в Object.prototype. Так само через constructor.prototype.
Наслідки:
- обхід перевірок:
if (user.isAdmin)для об'єкта, в якому поля немає, раптом даєtrue; - XSS у браузері: бібліотека читає налаштування з об'єкта опцій (
options.innerHTML,options.src), а «забруднене» значення підставляється в DOM. Так атакують через параметри URL (?__proto__[x]=...) з бібліотеками, що розбирають рядок запиту у вкладені об'єкти; - виконання коду на сервері (Node.js): забруднені опції
child_process, рушіїв шаблонів; - DoS - зламана логіка всього застосунку.
Захист:
- блокувати небезпечні ключі при злитті й записі за шляхом:
__proto__,constructor,prototype; - об'єкти без прототипу для словників з даних:
Object.create(null); Mapзамість звичайного об'єкта для довільних ключів;- валідація схемою (Zod) з «суворими» об'єктами - невідомі ключі відкидаються;
- оновлювати бібліотеки злиття, розбору рядка запиту, глибокого копіювання - багато відомих CVE саме тут (lodash
merge,qs, jQueryextend); Object.freeze(Object.prototype)- радикальний захист, але може зламати сторонні бібліотеки.
Чим це стосується PHP-розробника: фронтенд Laravel-застосунку (Vue, React, Alpine) і Node-скрипти збирання/SSR - теж частина поверхні атаки; оновлення npm-залежностей важливе так само, як composer.