Изследователи от Центъра за информационна сигурност на Гелмхолц (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, тъй като коренният прототип, използван в обекта o2, е променен чрез обект o1.
Пример за уязвим код: 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\/»; \/\/ тригер за извикване 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»; \/\/ тригер за извикване require («bytes»);
Изследователите анализираха 10 000 NPM пакета с най-много зависимости и установиха, че 1958 от тях нямат свойство main в package.json, 4420 използват относителни пътища в израза require, а 355 директно използват API за подмяна на команди.
Като работещ пример може да се даде експлойт за атака на бекенда на Parse Server, който преопределя свойството evalFunctions. За улесняване на идентифициране на подобни уязвимости е разработен инструментариум, комбиниращ методи на статичен и динамичен анализ. При тестване на Node.js бяха идентифицирани 11 гаджета, които могат да бъдат използвани за организиране на атаки, водещи до изпълнение на кода на атакуващия. Освен Parse Server, две експлоатиращи уязвимости също бяха установени в NPM CLI.
Източник: opennet.ru
