QuietPolyfill, инструмент для удаления файлов NPM, который возродил свою популярность.

QuietPolyfill: NPM-дроппер, возродившийся после кризиса.

TL, д-р

В конце июля и начале августа 2026 года в течение двух дней было опубликовано двадцать npm-пакетов, которые позиционируют себя как внутренние библиотеки компонентов пользовательского интерфейса и общие утилиты бэкэнда. Ни один из них не реализует заявленную функциональность. Каждый из них содержит заглушку класса, файл README, описывающий пакет как внутренний, и файл с именем _polyfill.js.

Самое интересное — это то, откуда именно запускается этот файл. Это не установочный хук. — в манифестах вообще не указано ни preinstall, ни install, ни postinstall. Вместо этого последняя строка index.js гласит: try { require("./_polyfill"); } catch (_) {}Полезная нагрузка запускается при первом импорте пакета в код приложения. Установка выполняется с помощью --ignore-scripts, standard Рекомендации по снижению рисков в цепочке поставок во время установки. не мешает этому.

Что _polyfill.js Процесс заключается в получении исполняемого файла, специфичного для платформы, с одного из трех хостов Cloudflare Workers, записи его во временный каталог под случайным именем, пометке его как исполняемого файла и запуске в отсоединении от родительского процесса. Этот исполняемый файл сам по себе является загрузчиком: он перечисляет продукты безопасности конечных точек по имени процесса, проверяет текущее имя пользователя по известным учетным записям среды анализа, обновляет интерфейсы телеметрии AMSI и ETW в памяти, устанавливает постоянное соединение под именем аудиокомпонента Windows и получает третий исполняемый файл размером в несколько мегабайт. Третий этап поступает в зашифрованном виде и Расшифровка и выполнение происходят в оперативной памяти, а не записываются на диск..

1 августа, примерно через четыре часа после того, как пакеты были подтверждены как вредоносные при ручной проверке, девятнадцать из двадцати названий были удалены из npm их собственным издателем в течение двадцати пяти секунд. 2 августа одно из этих названий было зарегистрировано снова — другим аккаунтом, с тем же кодом. На момент написания этого текста инфраструктура доставки обслуживала все три этапа.

Packages20 названий npm, ~74 опубликованные версии
Темы именованияbeaver-ui-* (13) accounts-* (3), плюс afisha-storybook-default, bcore-bravo-eslint-config, arbocrate-sla-prober-arbocrate-sla-prober-core, a.poltoradnev-package-c
ВызыватьИмпорт модуля (require), не является скриптом жизненного цикла npm.
Этапа 1._polyfill.js — извлечение и запуск
Этапа 2.Статически скомпилированный загрузчик, упакованный с помощью UPX на Linux; перечисление объектов, защищающих конечные точки, установка патчей AMSI/ETW, обеспечение постоянного доступа.
Этапа 3.beacon_linux.bin / beacon_mac.bin / update_win.exe4–13 МБ; доставляется в зашифрованном виде, выполняется в памяти.
ПлатформыLinux x64 и arm64, macOS universal, Windows x64
Издатели Два аккаунта, одна кодовая база
Статус19 из 20 имен не опубликованы оператором; 1 переиздано и опубликовано; инфраструктура доставки работает

Анатомия атаки

Этап 1 — упаковка

Все участники отправляют одинаковые файлы трехслойной формы. index.js экспортирует класс с в этом() который задает логическое значение, версия()И configure() Это объединяет объект параметров. Больше ничего. README.md предоставляет линию установки, требовать строка и предложение «Внутренний пакет. См. CONTRIBUTING.md для получения рекомендаций» — CONTRIBUTING.md отсутствует в архиве.

_polyfill.js Этот файл занимает чуть менее 5 КБ и читается как обычный инфраструктурный код. Он открывается с тремя проверками среды:

if (process.env.DISABLE_TELEMETRY ||
    process.env.ANALYTICS_OPT_OUT ||
    process.env.DO_NOT_TRACK)
  return;

Эти переменные представляют собой стандартные средства контроля конфиденциальности. Здесь они определяют, будет ли загружен и запущен исполняемый файл. Машина, которая устанавливает НЕ ОТСЛЕЖИВАТЬ полностью пропускается.

Далее следует штамп однократного выполнения: файл по адресу /tmp/.analytics_state или analytics_state недооценивают ее % TEMP% В Windows время модификации сравнивается с окном в 20 357 секунд. В течение этого окна модуль возвращает результат немедленно, поэтому повторные импорты во время сборки приводят к одной попытке получения данных, а не к десяткам.

Целевые хосты не представлены в виде литералов. Они собираются во время выполнения из фрагментов:

["oob-worker.cf102-","baf.workers.d","ev"].join("")

Таким образом создаются три хоста, которые перемешиваются перед использованием, поэтому порядок меняется при каждом выполнении. Получение данных осуществляется с помощью HTTPS GET-запроса, привязанного к IPv4. User-Agent of node-fetch/2.6 — правдоподобное значение, которое может быть отправлено процессом Node. Ответ размером менее 1,000 байт отбрасывается, и выполняется попытка подключения к следующему хосту.

Если все три варианта не сработают, модуль переключается на DNS. Он отправляет TXT-запрос для с. чтобы получить количество фрагментов, затем запрашивает 0. через н. Последовательно, пакетами по десять, ответы объединяются, а результат декодируется в Base64. Этот путь передает один и тот же исполняемый файл через DNS TXT-записи, что обеспечивает его работоспособность в средах, где исходящий HTTP-трафик фильтруется, а разрешение DNS — нет.

Полученные байты записываются в /var/tmp/.cache_<8-hex> в Unix или %TEMP%\dotnet_diag_<8-hex>.exe В Windows это имя файла, которое считывается как диагностический артефакт .NET. В Unix файлу присваивается права доступа 0755. Запуск осуществляется следующим образом: порождать с отсоединенный: истинный, stdio: “ignore” и unref()Таким образом, дочерний процесс переживает процесс Node, который его запустил; в Windows вызов добавляет windowsHide: true.

Две детали дополняют картину. Помощник, определяемый как const _l = (…a) => process.stderr.write(“”) Эта функция вызывается на каждом этапе получения данных — она записывает пустую строку и отбрасывает свои аргументы, занимая место, где обычно располагается лог. И каждый пакет содержит файл размером 81 КБ по адресу lib/telemetry.js В заголовке комментария описывается «Аналитический SDK» с «HTTPS-транспортом с ротацией конечных точек и повторными попытками» и «Обнаружением служб на основе DNS для обеспечения отказоустойчивости». В текущей сборке это ни к чему не приводит.

Этап 2 — погрузчик

Двоичный файл для Linux x64 представляет собой статически скомпилированный ELF-файл, сжатый с помощью UPX 3.96. Сборка для macOS — это универсальный двоичный файл, охватывающий архитектуры x86_64 и arm64; сборка для Windows — это упрощенный консольный исполняемый файл PE32+. Все четыре файла были получены и статически проверены; ни один из них не был запущен.

В таблице импорта и строк сборки для Windows поведение программы описано очень понятно. Она разрешает IsDebuggerPresent, CheckRemoteDebuggerPresent и NtQueryInformationProcess, и читает SYSTEM\CurrentControlSet\Services\Disk\Enum — путь в реестре, значения которого идентифицируют контроллеры виртуальных дисков. Он вызывает GetDiskFreeSpaceExW и GlobalMemoryStatusEx, что соответствует проверке соответствия ресурсов хоста кратковременному экземпляру анализа.

В нём содержится список имён пользователей: Текущий пользователь, ИТ-администратор, Хонг Ли, ТЕСТБОК, hapubws, милозс, Тимми, и, вредоносных программ, образец, вирус, охрана окружающей средыНекоторые из них являются артефактами общедоступных аналитических сред.

В нем также содержится список продуктов для защиты конечных точек и названия их процессов, перечисленных в соответствии с... CreateToolhelp32Снимок и Process32FirstW/Process32NextW:

  • ТолпаУдар Сокол (csfalconservice, CSFalconContainer)
  • Сажа (cb.exe)
  • Cylance (cylancesvc.exe)
  • Bitdefender (bdagent.exe)
  • Kaspersky (avp.exe, kavfs.exe)
  • Trend Micro и Deep Security (coreServiceShell, ds_agent.exe)
  • FireEye/Trellix (xagt.exe)
  • Tanium (taniumclient.exe)
  • HitmanPro (hmpalert.exe)
  • Эластичный (elastic-agent.exe, elastic-endpoint, filebeat.exe, winlogbeat.exe)
  • Вазух и OSSEC (wazuh-agent.exe, ossec-agent.exe)
  • Check Point
  • Defender для Windows

Присутствуют еще два символа: AmsiScanBuffer, точка входа, которую Windows предоставляет для сканирования содержимого в оперативной памяти, и EtwEventWriteФункция, с помощью которой Event Tracing for Windows записывает события. Оба являются распространенными целями для модификации внутри процесса, которая подавляет телеметрию, которую эти интерфейсы в противном случае производили бы.

В Windows функция сохранения данных отображается дважды. Двоичный файл копируется в... %APPDATA%\Microsoft\Windows\Themes\SystemSoundsService.exe, и два механизма ссылаются на него: значение ниже ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ\Microsoft\Windows\ТекущаяВерсия\Запуски запланированное задание, зарегистрированное как

schtasks /Create /F /SC ONLOGON
  /TN "\Microsoft\Windows\Multimedia\SystemSoundsService"
  /TR "\"%s\""
  /RL HIGHEST

Путь к задаче, имя задачи и имя исполняемого файла полностью соответствуют реальному аудиокомпоненту Windows. /RL ВЫСШИЙ запрашивает максимально возможный уровень привилегий.

Сборка для macOS следует тому же принципу с именами, соответствующими платформе: она записывает список свойств в ~/Library/LaunchAgents/com.apple.windowserver.helper.plist и размещает свой исполняемый файл по адресу ~/.local/share/runtime/com.apple.runtime, Это проверяет на /Applications/VMware Fusion.app и /Библиотека/Поддержка приложений/Инструменты VMware.

Возможности сети охватывают три типа транспорта: Wininet.dll для HTTPS с https://%s:%d%s формат строки, WS2_32.dll розетки и dnsapi.dll с DNSQuery_A рядом с с.%с.%с Строка формата — та же форма запроса с указанием количества фрагментов, которая используется на первом этапе.

Приведённое выше описание показывает, что импорт, строки и пути в реестре бинарного файла отображаются при проверке. Публичный многопроцессорный анализ того же образца Windows добавляет детали, которые невозможно установить с помощью статического чтения таблицы импорта, и это здесь указывается, а не переформулируется как непосредственное наблюдение. Этот анализ показывает, что ссылки на AMSI и ETW не просто разрешены, а исправлены — AmsiScanBuffer in amsi.dll и EtwEventWrite in ntdll.dll — что подавляет сканирование содержимого в памяти и трассировку событий для процесса. Кроме того, он сообщает, что проверки на защиту от анализа основаны на временных параметрах. CheckRemoteDebuggerPresentи определяет GetThreadContext Вызов функции проверки аппаратной точки останова путем чтения отладочных регистров. Он также сообщает об одноэкземплярном мьютексе. Global\{7A3B9E1F-C2D4-4F8A-B5E6-D7A1F0C3E2B9}и имена SystemSoundsService в качестве значения ключа запуска. Это значение мьютекса не было среди строк, восстановленных здесь — использованный фильтр извлечения исключал заключенные в фигурные скобки шестнадцатеричные токены в верхнем регистре — и оно записано в таблицу индикаторов на основании этого публичного анализа, а не прямого наблюдения.

Этапа 3.

Загрузчик ссылается на дополнительный набор путей на тех же хостах. Все они были доступны во время анализа: beacon_linux.bin при размере 5.3 МБ, beacon_mac.bin при размере 13.3 МБ, update_win.exe при размере 10.3 МБ и arm.bin Размер файла составляет 4.3 МБ. Эти файлы не были получены, и их поведение неизвестно.

Что касается пути для Windows, то в общедоступном анализе загрузчика описывается, как обрабатывается третий этап: он загружается через WinINet, поступает в зашифрованном виде, расшифровывается в памяти и выполняется в режиме рефлексии — выделяется память, разрешаются импорты, обрабатываются перемещения, и управление передается новому потоку. Создать потокПо этой причине данные третьего этапа никогда не записываются в файловую систему, поэтому при обнаружении угроз на основе файлов и проведении криминалистической экспертизы дисков после инцидента восстановить их будет нечего. Это также означает, что при извлечении байтов с хоста доставки будет получен зашифрованный текст, а не анализируемый исполняемый файл.

 

Лента

Все временные метки указаны в формате UTC и получены из метаданных реестра npm, которые регистрируют создание и удаление пакетов независимо от какого-либо наблюдателя.

После появленияЧто
2026-07-31 09:25:05 → 09:29:56Создано десять названий, каждое из которых начинается с версии. 0.0.1Примерно с интервалом в тринадцать секунд.
2026-08-01 09:38 → 11:29Существующие названия постепенно улучшаются благодаря завышенным номерам версий. 6.1.9 через 6.1.12, 9.9.9 через 9.9.12, 8.9.3 через 8.9.6, 5.4.6 через 5.4.9.
2026-08-01 13:04:14 → 13:04:18еще девять beaver-ui-* Имена создавались в течение четырех секунд, каждое публиковалось непосредственно в версии 12.x без какой-либо предыдущей истории.
2026-08-01 ~13:00–14:30Версии, классифицированные как вредоносные при ручной проверке.
2026-08-01 17:39:49 → 17:40:14Девятнадцать из двадцати неопубликованных имен были опубликованы в течение двадцати пяти секунд, последовательно, с интервалом примерно в 1.2 секунды.
2026-08-02 18:58:41a.poltoradnev-package-c создано заново под другой учетной записью сопровождающего, версия 0.0.2.
2026-08-02 19:20:28Версия 33.9.5 опубликовано под тем же названием.
2026-08-02 20:14Все три хоста доставки обслуживают второй и третий этапы; в качестве резервного DNS-сервера используется подсчет фрагментов.

Две из этих записей заслуживают более подробного рассмотрения.

В 13:04 за четыре секунды в реестр было добавлено девять пакетов, каждый с одной версией в диапазоне 12 секунд и без указания конкретной версии. 0.0.1 под ним. Номера версий в таком масштабе, для названий, которым всего один день, не описывают историю релизов.

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

 

Индикаторы компромисса

Идентификаторы хостов и пути обезличены. Не разрешайте и не получайте их из производственной системы.

ТипЗначение
курьерoob-worker[.]cf99-9b3[.]workers[.]dev
курьерoob-worker[.]cf102-baf[.]workers[.]dev
курьерoob-worker[.]cf103-070[.]workers[.]dev
Транспорт DNStin[.]dl[.]well1[.]site (Linux x64), tina[.] (arm64), ldr[.] (MacOS), win[.] (Windows) — количество фрагментов на c.<domain>данные по <n>.<domain>
Пути 2-го этапа/pkg/package, /pkg/package-arm64, /pkg/loader_mac, /pkg/package.exe
Пути 3-го этапа/pkg/beacon_linux.bin, /pkg/beacon_mac.bin, /pkg/update_win.exe, /pkg/arm.bin
Путь удаления (Unix)/var/tmp/.cache_<8-hex>
Путь для перетаскивания (Windows)%TEMP%\dotnet_diag_<8-hex>.exe
Сохранение данных (Windows)%APPDATA%\Microsoft\Windows\Themes\SystemSoundsService.exe; задача \Microsoft\Windows\Multimedia\SystemSoundsService; Значение ключа запуска SOFTWARE\Microsoft\Windows\CurrentVersion\Run\SystemSoundsService
Мьютекс (Windows)Global\{7A3B9E1F-C2D4-4F8A-B5E6-D7A1F0C3E2B9}
Сохранение данных (macOS)~/Library/LaunchAgents/com.apple.windowserver.helper.plist; двоичный в ~/.local/share/runtime/com.apple.runtime
Штамп, прошедший через несколько рядов/tmp/.analytics_state, %TEMP%\analytics_state (окно 20 357 с)
Запрос на подписьHTTPS GET, IPv4-закрепленный, User-Agent: node-fetch/2.6
Файл этапа 1_polyfill.js, SHA-256 392dc9ec85042ce1796bfa9b8c7bf6e05c7710579aab8b7b1eb73788683883af
Неуказанный файлlib/telemetry.js, SHA-256 2266c9b8e199daebe31a9afcb61d9b0d76706b63c92312fc8223d22de42e63c7
Этап 2 (Linux x64)SHA-256 7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3
Этап 2 (Linux arm64)SHA-256 aafc37589c0d21fb729761a1579ca3b278b95bce152aacfa29243ffd27f5eeb7
Этап 2 (macOS)SHA-256 c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00
Этап 2 (Windows)SHA-256 d632c2c0c7801c3da57cf849fe4b4ca8351d9a7fe73c31050bf5dbcc96a16791

Имена пакетов

beaver-ui-drawer beaver-ui-actions-button beaver-ui-card-large beaver-ui-date-range-picker beaver-ui-form beaver-ui-form-modal beaver-ui-form-object beaver-ui-grid beaver-ui-header beaver-ui-hooks beaver-ui-icon-lock beaver-ui-items-with-more beaver-ui-layout итоговая форма счетов accounts-loading-state хронология аккаунтов afisha-storybook-default bcore-bravo-eslint-config arbocrate-sla-prober-arbocrate-sla-prober-core a.poltoradnev-package-c

Атрибуция и наблюдаемое поведение

В деле участвуют два аккаунта npm. Девятнадцать неопубликованных имен были опубликованы одним из аккаунтов; a.poltoradnev-package-c Была проведена повторная регистрация 2 августа под другим именем учетной записи и другим доменом электронной почты. Код в обоих случаях имеет одинаковую историю, и вторая регистрация содержит те же хосты доставки, те же резервные домены DNS, ту же структуру пути передачи и тот же штамп однократного запуска.

Переизданная пара также демонстрирует, как она была собрана. Версии 0.0.2 и 33.9.5 Побайтово идентичны, за исключением строки версии. пакет.json, и index.js отправлено как 0.0.2 заявляет, const VERSION = “33.9.5”Оба артефакта были вырезаны из одного и того же дерева и впоследствии пронумерованы. Нет никаких данных. 0.0.2 история развития для 0.0.2 релиз для представления.

Нумерация версий для всего набора следует той же логике. Названия пакетов в этой кампании воспринимаются как объекты, существующие внутри организации, а не в общедоступном реестре: набор компонентов пользовательского интерфейса, конфигурация ESLint, инструмент проверки SLA, пакет, названный в честь человека. Когда сборка разрешает зависимость по имени и обращается к общедоступному реестру наряду с частным, побеждает версия с более высоким номером. Имя, созданное в 0.0.1 в понедельник и неся 12.9.6 Вторник станет подходящим днем ​​для такого сравнения.

Две волны публикаций также отличаются друг от друга, что наводит на мысль об итерации. В более раннем коде файл полезной нагрузки был назван соответствующим образом. setup.js и перечислил пять хостов доставки; текущий файл называется _polyfill.js и перечисляет три. В предыдущей версии удаленный файл удалялся через пять секунд после запуска, оставляя процесс запущенным с удаленного inode; в текущей версии этого не происходит. Неупомянутый lib/telemetry.js Остается в смоляном шаре на обоих концах, все еще отправляется, больше не подключен к сети.

Стоит отметить одно замечание относительно выключателей аварийного отключения.cisЭли, потому что это противоречит общепринятой точке зрения. ОТКЛЮЧИТЬ_ТЕЛЕМЕТРИ, ANALYTICS_OPT_OUT и НЕ ОТСЛЕЖИВАТЬ Они соблюдаются, и их проверяют, прежде чем что-либо еще произойдет. Исследователь, чья среда анализа устанавливает какой-либо из них, наблюдает пакет, который вообще ничего не делает.

Влияние и возможности защитников

Практические последствия срабатывания триггера во время импорта незначительны и заслуживают отдельного упоминания. npm install –ignore-scripts Это предотвращает выполнение скриптов жизненного цикла, и это разумный совет. В данном случае это не имеет эффекта, поскольку этот код не использует скрипт жизненного цикла. Полезная нагрузка выполняется, когда код приложения, запуск теста, сборщик или линтер впервые импортируют модуль. В среде CI, которая устанавливает модуль, а затем сразу же выполняет сборку, разница между этими двумя событиями составляет несколько секунд.

В случае поиска вредоносного ПО устойчивые сигналы носят скорее структурный, чем буквальный характер. Хосты, имена файлов и пути передачи будут меняться; эти формы сложнее изменить:

  • Точка входа в модуль, заключительным оператором которой является try { require(“./x”); } catch (_) {} вокруг файла, который не является частью экспортируемого API. Пакет с настоящим полифилом импортирует его в самом начале и использует полученный результат. Игнорирование всех ошибок, возникающих при импорте в конце файла, не является обработкой ошибок.
  • Строки хоста, собранные с помощью .присоединиться("") из множества фрагментов. В легитимном коде нет причин разделять имя хоста между элементами массива.
  • Пакет, объявленные зависимости которого пусты, но который пытается использовать... протокол HTTPS, DNS и дочерний_процесс во время импорта. Весь вывод заключается в извлечении и выполнении данных в пакете, который рекламирует компонент пользовательского интерфейса.
  • Номера версий не соответствуют возрасту пакета. Название, впервые опубликованное несколько дней назад в версии с двузначным номером, без каких-либо промежуточных релизов, описывает нечто иное, чем просто разработку.
  • Пишет в / Вар / TMP or % TEMP% следует CHMOD до 07:55 и отдельное место появления. Эта последовательность короткая, упорядоченная и редко бывает безобидной одновременно.

На хостах под управлением Windows есть две недорогие и надежные проверки. Артефакты сохранения данных представляют собой фиксированные строки, и ни один из них не должен присутствовать в чистой системе: запланированная задача в \Microsoft\Windows\Multimedia\SystemSoundsService, значение ключа запуска с именем SystemSoundsServiceи исполняемый файл с таким же именем под %APPDATA%\Microsoft\Windows\Themes\Подлинная служба звука Windows не находится ни в одном из этих мест. Во-вторых, загрузчик использует фиксированный одноэкземплярный мьютекс. Global\{7A3B9E1F-C2D4-4F8A-B5E6-D7A1F0C3E2B9}Его наличие на хосте является индикатором, и поскольку загрузчик рассматривает существующий мьютекс как «уже работающий», его предварительное создание является жизнеспособным краткосрочным способом внедрения, пока планируется устранение проблемы.

Обнаружение на третьем этапе — более сложная задача, и об этом стоит четко сказать. Согласно данным публичного анализа, данные расшифровываются и отображаются в памяти, а не записываются на диск, поэтому сканирование файлов и анализ диска их не обнаружат. Наблюдаемым остается собственный след загрузчика — записи о сохранении в системе, мьютекс и исходящие запросы к хостам доставки или доменам DNS-TXT.

Для сред, в которых зависимости разрешаются как из частного, так и из публичного реестра, предложенное здесь решение остается неизменным: необходимо ограничить область действия внутренних имен таким образом, чтобы публичное имя не могло их удовлетворить, и закрепить разрешение зависимостей во внутреннем реестре для этих областей. Описанное выше поведение, приводящее к завышению версий, работает только в тех случаях, когда более высокая публичная версия может получить имя.

Наконец, стоит внимательно прочитать не только заключение, но и сам факт удаления. Девятнадцать имен исчезли из реестра за двадцать пять секунд, а одно вернулось через день под новой учетной записью, при этом инфраструктура доставки осталась нетронутой. Удаление пакетов не означает прекращение операции; хосты продолжали обслуживать каждый этап, пока это писалось. Любое дерево зависимостей, которое разрешало одно из этих имен в период с 31 июля по 1 августа, следует рассматривать как имевшее возможность выполнить первый этап, независимо от того, существует ли это имя до сих пор.

Референсы

sca-инструменты-программное обеспечение-композиция-анализ-инструменты
Расставьте приоритеты, устраните и защитите риски, связанные с программным обеспечением
Получите бесплатный аккаунт.
Нет необходимости кредитную карту.

Защитите свою разработку и доставку программного обеспечения

с пакетом продуктов Xygeni