Исследователи компании Socket обнаружили скоординированную кампанию по компрометации цепочки поставок, которая затронула восемь пакетов в реестре Packagist. Злоумышленники внедрили вредоносный код не в ожидаемое место - composer.json, - а в package.json, который обычно ассоциируется с JavaScript-экосистемой. Такой межэкосистемный ход позволяет обойти внимание разработчиков и специалистов по безопасности, которые при аудите PHP-проектов сосредоточены на метаданных Composer и могут пропустить опасные хуки внутри JavaScript-файлов.
Описание
Как работала атака
Вредоносный скрипт, который выполнялся автоматически после установки пакета (так называемый postinstall-хук), скачивал бинарный файл из GitHub Releases - сервиса для распространения сборок на платформе GitHub. Команда curl использовалась с флагом -k, отключающим проверку TLS-сертификатов. Загруженный файл сохранялся под именем /tmp/.sshd - скрытым названием, напоминающим системный демон SSH. Затем скрипт делал файл исполняемым через chmod +x и запускал его в фоне с подавлением сообщений об ошибках (2>/dev/null). Все эти признаки - отказ от верификации сертификатов, маскировка под системный процесс, запуск без явного уведомления - однозначно указывают на вредоносную активность. Исследователям не удалось получить сам бинарник второй стадии, так как после обнаружения он был удалён с GitHub, однако первая стадия загрузки и запуска уже достаточна для классификации пакетов как опасных.
Какие пакеты пострадали
Socket обнаружил кампанию с помощью своего ИИ-сканера, который выявил идентичное подозрительное поведение в восьми версиях пакетов, отслеживающих ветки репозиториев: moritz-sauer-13/silverstripe-cms-theme (ветка dev-master), crosiersource/crosierlib-base (dev-master), devdojo/wave (dev-main), devdojo/genesis (dev-main), katanaui/katana (dev-main), elitedevsquad/sidecar-laravel (3.x-dev), r2luna/brain (dev-main) и baskarcm/tzi-chat-ui (dev-main). Во всех случаях в package.json был добавлен одинаковый postinstall-скрипт, а сами вредоносные коммиты были внесены непосредственно в исходные репозитории на GitHub. Некоторые мейнтейнеры уже откатили эти коммиты, но на момент проверки в нескольких репозиториях скрипт всё ещё присутствовал в основной ветке.
Масштаб выходит за пределы Packagist
Поиск по коду GitHub показал сотни результатов, связанных с той же учётной записью злоумышленника (parikhpreyash4), включая множество репозиториев на Node.js. Кроме того, тот же самый payload (вредоносная нагрузка) был обнаружен в файлах рабочих процессов GitHub Actions. Например, в публичных форках проектов UA2F и blog-1 в файлы .github/workflows/ci.yml и deploy_coding.yml была добавлена задача с именем Dependency Cache Sync, которая выполняла ту же команду загрузки и запуска бинарника из того же источника. Это указывает на то, что атакующий не ограничился одним механизмом исполнения - он использовал как postinstall-хуки в package.json, так и триггеры в CI/CD-пайплайнах.
Почему это особенно опасно для стартовых наборов
Наибольший практический риск несут два пакета: devdojo/wave (около 6400 звёзд на GitHub) и devdojo/genesis (около 1300 звёзд и более 9000 установок через Packagist). Оба являются стартовыми наборами для Laravel - популярного PHP-фреймворка. Проблема в том, что когда разработчик клонирует такой стартовый набор и делает его корнем своего проекта, package.json оказывается на верхнем уровне. Тогда стандартная команда npm install запускает postinstall-скрипт непосредственно. В случае с обычными библиотеками, которые подключаются как зависимость в папку vendor, postinstall-хук не выполняется, так как npm обрабатывает скрипты только для корневых зависимостей. Таким образом, стартовые наборы становятся идеальной целью.
Остальные шесть затронутых пакетов имеют гораздо более узкую аудиторию, что не делает атаку безобидной, но концентрация реальной угрозы приходится именно на проекты-шаблоны.
Ответные меры и сложности исправления
Socket сообщил о находках администраторам Packagist, и они немедленно удалили затронутые пакеты. Однако из-за того, что большинство версий были branch-tracking (отслеживание веток), а не фиксированными релизами, ситуация осложняется. Packagist может восстановить такие пакеты при следующем обновлении, если upstream-репозиторий всё ещё содержит вредоносный коммит. Поэтому простой идентификации по номеру версии недостаточно: необходимо проверять конкретный коммит или архив пакета.
Что делать специалистам по безопасности
Команды, использующие пакеты из Packagist, которые включают JavaScript-инструменты сборки, должны проверять не только composer.json, но и содержимое package.json. Особенно это касается зависимостей, указанных в виде branch-tracking (ветки dev-main, dev-master), так как их содержимое может меняться при каждом обновлении. Даже без доступа ко второй стадии вредоносная установка сама по себе предоставляет удалённое выполнение кода во время установки или сборки и пытается скрыть свою активность - отключением TLS-верификации, подавлением ошибок и маскировкой файла под системный процесс. Игнорировать такие сигналы нельзя.
Индикаторы компрометации
GitHub account
- parikhpreyash4
GitHub repository
- parikhpreyash4/systemd-network-helper-aa5c751f
Payload URL
- https://github.com/parikhpreyash4/systemd-network-helper-aa5c751f/releases/latest/download/gvfsd-network
Payload File Path
- /tmp/.sshd
Suspicious command fragments
- curl -skL
- chmod +x /tmp/.sshd
- /tmp/.sshd &