Atak na Node.js poprzez manipulację prototypami obiektów JavaScript

Badacze Centrum Helmholtza ds. bezpieczeństwa informacji (CISPA) oraz Królewskiego Instytutu Technologicznego (Szwecja) przeanalizowali zastosowanie techniki zanieczyszczania prototypów obiektów JavaScript („prototype pollution”) do tworzenia ataków na platformę Node.js oraz popularne aplikacje na jej podstawie, prowadzących do wykonania kodu.

Metoda zanieczyszczania prototypu wykorzystuje cechę języka JavaScript, która pozwala na dodawanie nowych właściwości do korzennego prototypu dowolnego obiektu. W aplikacjach mogą występować fragmenty kodu (gadżety), na które wpływa wstawiona właściwość; na przykład w kodzie może być konstrukcja w rodzaju 'const cmd = options.cmd || "/bin/sh"', której logika działania zostanie zmieniona, jeśli atakujący będzie mógł wstawić właściwość "cmd" do korzennego prototypu.

Aby skutecznie przeprowadzić atak, konieczne jest, aby w aplikacji dane pochodzące z zewnątrz mogły być używane do tworzenia nowej właściwości w korzennym prototypie obiektu, a także aby w trakcie wykonania występował gadżet, zależny od zmienionej właściwości. Zmiana prototypu realizowana jest dzięki obróbce w Node.js właściwości „__proto__” i „constructor”. Właściwość „__proto__” zwraca prototyp klasy obiektu, a właściwość „constructor” zwraca funkcję, używaną do tworzenia obiektu.

Jeśli w kodzie aplikacji pojawia się przypisanie „obj[a][b] = value” i wartości są pobierane z danych zewnętrznych, atakujący może ustawić „a” na wartość „__proto__” i osiągnąć ustawienie swojej właściwości o nazwie „b” i wartości „value” w korzennym prototypie obiektu (obj.__proto__.b = value;), przy czym właściwość ustawiona w prototypie będzie widoczna we wszystkich obiektach. Podobnie jeśli w kodzie występują wyrażenia w rodzaju „obj[a][b][c] = value”, ustawiając „a” na wartość „constructor”, a „b” na „prototype”, we wszystkich istniejących obiektach można zdefiniować nową właściwość o nazwie „c” i wartości „value”.

Przykład zmiany prototypu: const o1 = {}; const o2 = new Object(); o1.__proto__.x = 42; // tworzymy w korzennym prototypie właściwość „x” console.log(o2.x); // odnosimy się do właściwości „x” z innego obiektu // na wyjściu otrzymamy 42, ponieważ przez obiekt o1 zmieniono korzenny prototyp, używany również w obiekcie o2

Przykład podatnego kodu: function entryPoint(arg1, arg2, arg3){ const obj = {}; const p = obj[arg1]; p[arg2] = arg3; return p; }

Jeśli argumenty funkcji entryPoint są formowane na podstawie danych wejściowych, atakujący może przekazać w arg1 wartość '__proto__' i stworzyć w głównym prototypie właściwość o dowolnej nazwie. Jeśli przekażesz w arg2 wartość 'toString', a w arg3 - 1, możesz zdefiniować właściwość 'toString' (Object.prototype.toString=1) i spowodować awarię aplikacji podczas wywoływania funkcji toString().

Przykładem sytuacji, które mogą prowadzić do wykonania kodu atakującego, jest tworzenie właściwości 'main', 'shell', 'exports', 'contextExtensions' i 'env'. Na przykład atakujący może stworzyć w głównym prototypie obiektu właściwość 'main', zapisując w niej ścieżkę do swojego skryptu (Object.prototype.main = './../../../pwned.js'), a ta właściwość zostanie wywołana w momencie wykonania kodu w konstrukcji require('my-package'), jeśli dołączany pakiet wyraźnie nie określa w package.json właściwości 'main' (jeśli właściwość nie jest określona, zostanie ona pobrana z głównego prototypu). Podobnie mogą być podstawione właściwości 'shell', 'exports' i 'env': let rootProto = Object.prototype; rootProto['exports'] = {'.': './changelog.js'}; rootProto['1'] = '/path/to/npm/scripts/'; // wywołanie funkcji 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'; // wywołanie funkcji require('bytes');

Badacze przeanalizowali 10 tysięcy pakietów NPM, które mają najwięcej zależności, i odkryli, że 1958 z nich nie ma właściwości main w package.json, 4420 używa względnych ścieżek w wyrażeniu require, a 355 bezpośrednio korzysta z API do podstawiania poleceń.

Przykładem działającego exploita mogą być ataki na backend Parse Server, które nadpisują właściwość evalFunctions. Aby uprościć identyfikację podobnych podatności, opracowano narzędzia łączące metody analizy statycznej i dynamicznej. W toku testów Node.js zidentyfikowano 11 gadgetów, które można wykorzystać do przeprowadzania ataków prowadzących do wykonania kodu atakującego. Oprócz Parse Server, dwie exploatowalne podatności zostały również zidentyfikowane w NPM CLI.

Źródło: opennet.ru

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster