Исследователи Socket нашли вредоносный JavaScript в dev-версии PHP-пакета roberts/leads, доступной через Packagist. Код был спрятан в файле tailwind.js, который выглядел как обычная конфигурация Tailwind CSS.

Заражённая часть находилась в конкретной development-ветке drewroberts/feature/test-case, которую Packagist показывал как устанавливаемую версию dev-drewroberts/feature/test-case.
Такой формат хорошо подходит для точечной атаки на разработчика. Жертве не нужно случайно установить популярный пакет из общей выдачи. Достаточно получить «тестовое задание», «проект для интервью» или «быстрый фикс» с командой вида:composer require roberts/leads:dev-drewroberts/feature/test-case
После выполнения такой команды разработчик сам запускает чужой код в рабочем окружении. Для кампаний против инженеров это почти идеальный сценарий: всё выглядит как обычная проверка проекта перед собеседованием.
Вредоносный код находился в tailwind.js. В начале файл выглядел безобидно: стандартный экспорт конфигурации Tailwind, пустые секции purge, theme, variants и plugins.
Основная нагрузка была спрятана дальше — после большого блока пробелов справа от видимой части файла. При обычном просмотре в редакторе или веб-интерфейсе разработчик мог увидеть только нормальную конфигурацию и не заметить добавленный JavaScript без горизонтальной прокрутки.
После легитимного начинался другой код. Он не имел отношения к Tailwind. Внутри был маркер кампании: global['!']='9-0264-2'
Затем шла обфускация, восстановление внутренних объектов Node.js, получение данных из публичной блокчейн-инфраструктуры, расшифровка payload через XOR и выполнение результата через eval().
Это полноценный загрузчик, замаскированный под файл разработки.
PHP-проект может использовать JavaScript-инструменты для сборки фронтенда, CSS, ассетов и Tailwind-конфигурации. Поэтому наличиеtailwind.jsв Laravel- или PHP-пакете не выглядит странно.
Разработчик открывает проект, видит привычные файлы, ставит зависимости через Composer, запускает сборку или тесты. В этот момент вредоносный JavaScript может выполниться в Node.js-окружении.
Опасность в смешанной современной разработке: PHP, Composer, Node.js, Tailwind, Vite, GitHub Actions, .env, локальные SSH-ключи, токены npm, ключи облаков и CI-секреты часто живут рядом. Один проект может дать атакующим доступ к нескольким контурам сразу.
Если payload запускается на рабочей машине разработчика, ему становятся доступны переменные окружения, локальные файлы, ключи, токены, Git-метаданные и сетевые возможности процесса. Если тот же код выполняется в CI, риск смещается к секретам сборки, деплоя и облачной инфраструктуры.
Локальный код в tailwind.js сам не занимался прямой кражей файлов. Его задача — получить следующий этап и выполнить его.
Загрузчик обращался к публичной блокчейн-инфраструктуре: TRON, Aptos и BNB Smart Chain. Это схема dead drop. Злоумышленник не держит обычный управляющий домен, который можно быстро заблокировать. Вместо этого вредоносный код читает данные из транзакций или связанных с ними записей и достаёт оттуда указатель или зашифрованный материал.
Механика выглядела так: сначала loader пытался получить данные через TRON. Если не получалось, использовал Aptos как запасной путь. Затем забирал данные через BNB Smart Chain RPC, расшифровывал их с заданными XOR-ключами и выполнял как JavaScript.
В одном из этапов код мог запускать скрытый дочерний процесс Node.js через child_process.spawn() с флагом windowsHide: true. На Windows это помогает убрать видимое окно и оставить процесс работать фоново.
Для защитников это неудобная схема. В логах нет очевидного домена вида evil-c2.example. Есть обращения к публичным blockchain/RPC-сервисам, которые сами по себе могут быть легитимными для некоторых проектов. Поэтому важен контекст: Node.js в build-процессе внезапно ходит в TRON, Aptos или BNB RPC, затем запускает node -e и скрытый дочерний процесс.
Socket оценивает инцидент как вероятную targeted-кампанию, похожую на Contagious Interview. Это серия атак, где разработчиков заманивают фейковыми вакансиями, тестовыми заданиями или рабочими чатами, а затем заставляют запустить вредоносный проект локально.
Признаки:
-
Вредоносный код лежал не в стабильной версии, а в dev-ветке.
-
Dev-версию обычно ставят явно, по конкретной инструкции.
-
Название ветки
feature/test-caseхорошо вписывается в легенду «проверьте тестовый кейс». -
Payload спрятан в обычном конфигурационном файле.
-
Loader использует blockchain dead drop, который уже встречался в связанных кампаниях.
-
Маркер 9-0264-2 пересекается с другими известными цепочками, где фигурировали DEV#POPPER RAT, OmniStealer и BeaverTail.
Для массовой атаки такая схема неудобна. Случайный пользователь редко ставит dev-ветку PHP-пакета. Для точечной атаки — наоборот. Рекрутер или «техлид» в чате присылает команду, разработчик запускает ее ради интервью, и заражение проходит без фишингового вложения.
Famous Chollima — северокорейская группа известная двумя направлениями:
Проникновение в организации через фальшивых сотрудников. В таких схемах люди с поддельными биографиями устраиваются в компании или пытаются получить доступ к рабочей инфраструктуре под видом легитимных специалистов.
Атаки на внешних разработчиков через фейковые вакансии, тестовые задания и open source-проекты. Это направление как раз связано с Contagious Interview. Разработчику предлагают выполнить задачу, скачать репозиторий, поставить зависимости, запустить тесты. Внутри проекта находится loader или инфостилер.
Новая находка важна тем, что показывает расширение за пределы привычного npm-фокуса. Вредоносные пакеты и тестовые проекты для JavaScript-разработчиков уже стали регулярной темой. Здесь атакующая логика пришла в PHP-экосистему через Packagist и Composer.
Packagist — основной репозиторий пакетов для PHP. Composer подтягивает зависимости именно оттуда. Для PHP-разработчика команда composer require выглядит так же обычно, как npm install для JavaScript-разработчика.
Dev-версии в Composer — нормальный механизм. Они нужны для тестирования веток, предварительных изменений и интеграций до релиза. В composer-пакетах часто встречаются constraints вроде dev-main, dev-master, dev-feature/*.
Именно это делает канал удобным для атаки. Dev-ветка может быть легитимной, пакет — настоящим, maintainer — известным, репозиторий — не новым. Вредоносным оказывается не весь проект, а один конкретный путь установки.
Socket отдельно отмечает, что стабильная ветка roberts/leads не показывала тех же признаков при проверке. Это снижает масштаб инцидента, но не отменяет риска для тех, кто получил точную инструкцию установить заражённую dev-версию.
Видимый loader в tailwind.js не был финальным вредоносом. Его роль — доставить и выполнить код, который может меняться без изменения пакета.
После запуска в Node.js удалённый payload потенциально получает доступ к:
-
переменным окружения, включая облачные ключи и CI-секреты;
-
локальным .env-файлам;
-
SSH-ключам;
-
токенам package registry;
-
Git-метаданным и учётным данным;
-
исходному коду проекта;
-
сетевым API;
-
запуску дополнительных процессов.
Для разработчика это худший тип компрометации. Машина часто содержит сразу несколько уровней доверия: личные ключи, корпоративные репозитории, доступ к staging, иногда доступ к production, токены GitHub, GitLab, npm, Packagist, Docker registry, cloud CLI и секреты локальных тестовых окружений.
Один запуск «тестового задания» может превратиться в компрометацию не только ноутбука, но и организации.
Затронутая версия Packagist:dev-drewroberts/feature/test-case
Связанная GitHub-ветка: drewroberts/feature/test-case
Затронутый файл: tailwind.js
Коммит ветки: 6c5c3c7655ce76399af11126b7e9a9058eb2e45d
SHA-256 архива: 522b28a2f78771715497ba53729d4ab9a50e982322c391379f3bddf7c8cb363f
SHA-256 файла tailwind.js: 96afdba882046385242cbed46871e41147c8055c5d9eff7460847b2c01a77dc3
TRON-кошельки, использованные как dead drop:
TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAPTXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG
Aptos-идентификаторы для fallback:
0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3
XOR-ключи:
2[gWfGj;<:-93Z^Cm6:tTh^D)cBz?NM]
Маркер кампании: global['!']='9-0264-2'
Одних индикаторов мало. Ветки могли удалить, payload — измениться, а инфраструктура через публичные блокчейны усложняет блокировку. Полезнее искать поведение: Node.js в процессе сборки обращается к TRON, Aptos или BNB RPC, выполняет eval(), запускает node -e, создаёт скрытый дочерний процесс и читает переменные окружения.
PHP-разработчикам стоит проверить:
-
Историю Composer. Если в последние дни вы ставили roberts/leads именно как dev-drewroberts/feature/test-case, окружение нужно считать потенциально скомпрометированным.
-
Локальные токены и ключи. Нужно проверить и при необходимости перевыпустить GitHub/GitLab-токены, SSH-ключи, npm/Packagist-токены, cloud credentials, ключи Docker registry и секреты, которые могли находиться в .env.
-
Оболочку разработки. Лучше проверить историю команд, процессы Node.js, неизвестные фоновые процессы, автозапуск, недавние изменения в shell-профилях, npm scripts, Composer scripts и Git hooks.
-
Репозитории. Нужно посмотреть, не появились ли неизвестные коммиты, новые deploy keys, OAuth-приложения, GitHub Actions secrets, webhooks и подозрительные workflow-файлы.
-
CI. Если dev-ветка запускалась в CI, нужно считать скомпрометированными секреты, доступные этому job. Длинноживущие ключи лучше заменить, даже если прямой эксфильтрации не видно.
Для PHP- и JavaScript-смешанных проектов стоит проверять не только composer.json.
Особое внимание нужно уделять:
-
package.json;
-
composer.json;
-
tailwind.config.*;
-
postcss.config.*;
-
vite.config.*;
-
next.config.*;
-
.github/workflows/*;
-
scripts/*;
-
Git hooks;
-
Dockerfile и docker-compose-файлам.
Опасный признак — код, спрятанный после большого блока пробелов, странные обращения к global, восстановление require, eval(), Function(), child_process.spawn, node -e, обращения к RPC-сервисам блокчейнов и загрузка кода во время сборки.
Для интервью и тестовых заданий это особенно важно. Любая просьба «просто запустить проект» — это просьба выполнить чужой код на вашей машине.
Мейнтейнерам PHP-пакетов нужно проверить защиту репозитория и связку GitHub → Packagist. Полезно посмотреть:
-
branch protection rules;
-
список collaborators;
-
deploy keys;
-
GitHub personal access tokens;
-
OAuth-приложения;
-
Packagist webhooks;
-
автоматические обновления пакета;
-
старые ветки, которые Packagist может показывать как устанавливаемые dev-версии.
Зараженная dev-ветка опасна ещё и репутационно. Стабильный релиз может быть чистым, но пользователи увидят название пакета в новости и решат, что проект полностью вредоносный. Поэтому важно быстро публиковать короткое объяснение: что затронуто, что не затронуто, какие версии безопасны, какие действия стоит предпринять пользователям.
Есть новость? Станьте автором.
Мы сотрудничаем с независимыми исследователями и специалистами по кибербезопасности. Отправьте нам новость или предложите статью на рассмотрение редакции.