Атака на Node.js через маніпуляції із прототипами об'єктів JavaScript

Дослідники Центру Гельмгольця з інформаційної безпеки (CISPA) та Королівського технологічного інституту (Швеція) проаналізували застосування техніки засмічення прототипу об'єктів JavaScript («prototype pollution») для створення атак на платформу Node.js та популярні програми на її основі, що призводять до виконання коду.

Метод засмічення прототипу використовує особливість JavaScript, що дозволяє додати нові властивості в кореневий прототип будь-якого об'єкта. У додатках можуть зустрічатися блоки коду (гаджети), на роботу яких впливає підставлена ​​властивість, наприклад, код може бути конструкція виду 'const cmd = options.cmd || "/bin/sh"', логіка роботи якої буде змінена, якщо атакуючий зуміє підставити властивість "cmd" в кореневий прототип.

Для успішного здійснення атаки потрібно, щоб у додатку надходять ззовні дані могли використовуватися для створення нової властивості в кореневому прототипі об'єкта, а також у ході виконання зустрічався гаджет, що залежить від зміненої властивості. Зміна прототипу здійснюється завдяки обробці в Node.js службових властивостей "__proto__" та "constructor". Властивість __proto__ повертає прототип класу об'єкта, а властивість constructor повертає функцію, використовувану для створення об'єкта.

Якщо в коді програми зустрічається присвоєння «obj[a][b] = value» і значення виставляються із зовнішніх даних, атакуючий може виставити «a» у значення «__proto__» та домогтися встановлення своєї властивості з ім'ям «b» та значенням «value» у кореневому прототипі об'єкта (obj.__proto__.b = value;), при цьому виставлена ​​в прототипі властивість буде видно у всіх об'єктах. Аналогічно, якщо в коді зустрічаються вирази виду «obj[a][b][c] = value», виставивши «a» у значення «constructor», а «b» у «prototype» у всіх існуючих об'єктах можна визначити нову властивість з ім'ям "c" та значенням "value".

Приклад зміни прототипу: const o1 = {}; const o2 = new Object (); o1.__proto__.x = 42; // Створюємо в кореневому прототипі властивість "x" console.log (o2.x); // звертаємося до властивості «x» з іншого об'єкта // на виході отримаємо 42, оскільки через об'єкт o1 було змінено кореневий прототип, використовуваний зокрема й у об'єкті o2

Приклад вразливого коду: function entryPoint (arg1, arg2, arg3) {const obj = {}; const p = obj [arg1]; p [arg2] = arg3; return p; }

Якщо аргументи функції entryPoint формуються з вхідних даних, то атакуючий може передати в arg1 значення __proto__ і створити в кореневому прототипі властивість з будь-яким ім'ям. Якщо передати в arg2 значення "toString", а в arg3 - 1, можна визначити властивість "toString" (Object.prototype.toString = 1) і домогтися краху програми під час виклику функції toString ().

Як приклад ситуацій, які можуть призвести до виконання коду атакуючого, наводиться створення властивостей "main", "shell", "exports", "contextExtensions" та "env". Наприклад, атакуючий може створити в кореневому прототипі об'єкта властивість «main», записавши шлях до свого скрипту (Object.prototype.main = «./../../pwned.js») і дана властивість буде викликана в момент виконання в коді конструкції require(«my-package»), якщо пакет, що підключається, явно не визначає в package.json властивість «main» (якщо властивість не визначено, воно буде отримано з кореневого прототипу). Аналогічно можуть бути підставлені властивості "shell", "exports" та "env": let rootProto = Object.prototype; rootProto[«exports»] = {«.»:»./changelog.js»}; rootProto["1"] = "/path/to/npm/scripts/"; // trigger call require ("./target.js"); Object.prototype.main = "/path/to/npm/scripts/changelog.js"; Object.prototype.shell = "node"; Object.prototype.env = {}; Object.prototype.env.NODE_OPTIONS = «inspect-brk=0.0.0.0:1337»; // trigger call require ("bytes");

Дослідники проаналізували 10 тисяч NPM-пакетів, що мають найбільше залежностей, і виявили, що 1958 з них не мають властивості main в package.json, 4420 використовують відносні шляхи у виразі require, а 355 безпосередньо використовують API для підстановки команд.

Як працюючий приклад можна навести експлоїт для атаки на бекенд Parse Server, що перевизначає властивість evalFunctions. Для спрощення виявлення подібних уразливостей розроблено інструментарій, що комбінує методи статичного та динамічного аналізу. У ході тестування Node.js було виявлено 11 гаджетів, які можна використовувати для організації атак, що призводять до виконання атакуючого коду. Крім Parse Server, дві експлуатовані вразливості також було виявлено у NPM CLI.

Джерело: opennet.ru

Додати коментар або відгук