42 вредоносных пакета в RubyGems: код ждёт 40 минут и обходит проверки безопасности

42 вредоносных пакета в RubyGems: код ждёт 40 минут и обходит проверки безопасности

5 октября 2026 года в репозитории RubyGems за один час появились 42 пакета для языка Ruby. Публикация шла с одной учётной записи, а сами пакеты рассчитаны на разработчиков криптовалютных проектов. RubyGems - основной источник библиотек для проектов на Ruby, и реестр уже не первый год привлекает атакующих: одна удачная публикация расходится по тысячам сборочных систем. Пакеты попадают в систему во время установки зависимостей, когда разработчик или автоматическая сборка сама запускает пакетный менеджер. Сорок два пакета за час - плотный поток для реестра, где публикации идут размеренно.

Описание

Названия подбирались по двум схемам. Двадцать шесть пакетов выдавали себя за утилиты для Ethereum и Bitcoin: разбор ключей, работа с кошельками, словари для восстановления доступа. Ещё шестнадцать построены по принципу регистрации имён с опечатками (тайпосквоттинг) - в названии известной библиотеки переставлены буквы или добавлен лишний символ. Имена с опечатками встречаются в реестрах постоянно, и распознать их без сверки трудно. Расчёт на спешку и на доверие к реестру: разработчик берёт похожее имя из файла зависимостей коллеги и не сверяет его с оригиналом.

Опасный код срабатывает при установке, и опирается он на штатный механизм. Пакет вправе объявить нативное расширение, тогда пакетный менеджер запускает служебный скрипт для подготовки сборки. Такой скрипт прописали все 42 пакета, и он выполнял чужой код. Полезная начинка библиотек служила приманкой: часть пакетов действительно разбирала зашифрованные файлы криптокошельков и ничего враждебного не делала. Такой пакет проходит беглый осмотр, ведь внутри лежит осмысленный код, а не пустая заготовка. В тридцати одном пакете для правдоподобия лежала ещё и пустая заготовка на языке C. Объявленное расширение фигурировало в описании каждого пакета - в этом и состоит ключевая деталь схемы.

По данным анализа инцидента, обе версии скрипта сначала проверяли, попали ли они на настоящую рабочую станцию. Код останавливался, если находил признаки автоматической сборки и тестирования, видел в имени узла намёк на контейнер или песочницу, встречал сгенерированное имя пользователя либо запускался из временного каталога. Потом следовали два встречных условия. Система должна работать больше получаса. В домашнем каталоге должны лежать следы разработчика: настройки системы контроля версий, файлы с параметрами доступа к пакетным реестрам, ключи удалённого входа. Сканеру, который ставит пакет в свежий контейнер, не подходит ни одно условие. Он фиксирует обычную установку без сетевой активности.

Если проверки пройдены, пакет собирает из закодированной строки исполняемый код. Затем он дважды отделяет процесс от родительского, создаёт собственную сессию и отключает стандартный вывод. Такая подготовка нужна, чтобы код пережил завершение установки и не зависел от терминала. Первый вариант скрипта доводит сборку до конца, поэтому установка ничем себя не выдаёт. Дальше наступает пауза от 20 до 40 минут. Установка завершается без видимых ошибок, а в одиннадцати случаях вообще без единого сообщения. Пауза нужна, чтобы код не попал под автоматический анализ: системы наблюдения обычно следят за пакетом лишь несколько минут. Во второй версии скрипта нет штатной части, отвечающей за подготовку сборки, поэтому установка может закончиться ошибкой. Полезная нагрузка стартует раньше, и на неё это не влияет.

Полезные нагрузки делятся на два типа. Одиннадцать пакетов открывают обратную командную оболочку на удалённый сервер. Злоумышленник получает управление в правах того пользователя, который запустил установку. При обрыве связи подключение повторяется каждые 30 секунд. Все одиннадцать пакетов отличаются друг от друга только номером сетевого порта. Тридцать один пакет ведёт себя иначе: скачивает архив с того же сервера, распаковывает во временный каталог, запускает установочный сценарий и стирает файлы. Адрес хранится в зашифрованном виде и собирается в момент запуска. Служебные переменные окружения позволяют сократить паузу до двух секунд - вероятно, для отладки. Два сценария, по всей видимости, решают разные задачи: один даёт немедленный доступ к машине, второй разворачивает дополнительный набор инструментов. Что делает скачанный сценарий, неизвестно.

Компрометация рабочей машины разработчика приводит к серьёзным последствиям. На ней обычно лежат доступы к репозиториям исходного кода, ключи подписи сборок, токены публикации пакетов, учётные записи облачных консолей и криптокошельки. Для компаний, выпускающих собственные библиотеки, риск шире: с рабочей станции злоумышленник добирается до цепочки поставок и внедряет код в продукты, которыми пользуются клиенты. Обнаружить схему сложно, и дело не только в задержке. Вредоносный код не хранится в пакете в готовом виде, он собирается из закодированной строки прямо на машине жертвы. Поиск по известным образцам в файлах даёт мало. Установка выглядит обычной операцией, а сетевое соединение возникает через десятки минут после неё. Надёжный источник данных - журналы процессов и сетевых подключений на самих рабочих станциях.

Меры защиты остаются привычными, но требуют дисциплины. Версии зависимостей нужно фиксировать; имена библиотек - сверять с официальным реестром; сборку - проводить в изолированных окружениях с минимальными правами. Право публиковать пакеты разумно выдавать ограниченному кругу сотрудников и защищать двухфакторной аутентификацией. Проверять стоит не только новые зависимости, но и те, что уже стоят в проекте. Отдельного внимания требуют машины, где пакеты ставили вручную, а не в изолированной среде. Если пакет всё же установлен, машину и её учётные данные придётся считать скомпрометированными: отозвать ключи и токены, сменить пароли, проверить журналы сетевых подключений. Направление атак на реестры меняется - простые подделки уступают схемам, которые умеют отличать живое рабочее место от песочницы. Автоматическое обнаружение таких публикаций заметно усложняется.

Индикаторы компрометации

Комментарии: 0