Киберпреступность

Famous Chollima добралась до PHP-разработчиков: вредоносный код спрятали в dev-ветке Packagist-пакета

Маша Даровская
By Маша Даровская , IT-редактор и автор
Famous Chollima добралась до PHP-разработчиков: вредоносный код спрятали в dev-ветке Packagist-пакета
Обложка © Anonhaven

Исследователи 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:

TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP
TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG

Aptos-идентификаторы для fallback:

0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e
0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3

XOR-ключи:

2[gWfGj;<:-93Z^C
m6: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.js;

  • tailwind.config.*;

  • postcss.config.*;

  • vite.config.*;

  • webpack.mix.js;

  • 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-ветка опасна ещё и репутационно. Стабильный релиз может быть чистым, но пользователи увидят название пакета в новости и решат, что проект полностью вредоносный. Поэтому важно быстро публиковать короткое объяснение: что затронуто, что не затронуто, какие версии безопасны, какие действия стоит предпринять пользователям.

Есть новость? Станьте автором.

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