В Rust-крейт arrayref внедрили вредонос, который запускался при сборке проекта

В Rust-крейт arrayref внедрили вредонос, который запускался при сборке проекта

20 августа 2026 года на реестре crates.io появилась скомпрометированная версия 0.3.10 популярного Rust-крейта arrayref. Она добавляла всего одну новую строку в манифест, зависимость от крейта proc-macro1, чьё название представляет собой типосквоттинг (намеренно похожее написание) на настоящий и широко используемый крейт proc-macro2. Сборочный скрипт версии 1.0.107 этого поддельного крейта при компиляции проекта скачивает и запускает вредоносный бинарник, то есть вредонос срабатывает у любого, кто просто собирает (cargo build) проект, тянущий за собой заражённые версии. Сам код arrayref (четыре макроса) не менялся и вредоноса не содержит: вся вредоносная логика лежит в сборочном скрипте proc-macro1, а Cargo, как поясняет источник, собирает все объявленные обязательные зависимости независимо от того, обращается ли к ним код проекта, то есть одной строки в манифесте было достаточно, чтобы Cargo подтянул и собрал proc-macro1 при любой сборке с arrayref 0.3.10. Вредоносные версии crates.io уже удалила.

Аккаунт droundy, мейнтейнера настоящего arrayref, а также крейта append-only-vec, по всей видимости, был взломан: репозитории arrayref и append-only-vec на GitHub, как и весь аккаунт droundy, сейчас отвечают 404, так что исходный код напрямую уже не проверить. Вредоносный proc-macro1 опубликован с отдельного аккаунта dtolney, чьё написание намеренно похоже на настоящий аккаунт dtolnay, принадлежащий Дэвиду Толнаю. Метаданные пакета подделывают поле авторства под его именем и указывают в поле repository несуществующий путь dtolnay/proc-macro1, который тоже отвечает 404; email в этих метаданных, по данным источника, Толнаю не принадлежит. При этом сам каталог src/ внутри proc-macro1, это фактически код proc-macro2 с механической заменой имени крейта, вплоть до ссылок в документации и даже скопированных номеров issue на GitHub, так что библиотека работала как полноценная замена оригинала, и сборка проектов не ломалась, пока в фоне отрабатывал вредоносный сборочный скрипт, это и делало подделку менее заметной при обычной проверке. Заметное отличие от настоящего proc-macro2 было в зависимостях самой сборки: proc-macro1 добавляет три дополнительные библиотеки, которые дают скрипту декодирование base64, работу с TLS и HTTP-клиент, ровно то, что нужно, чтобы скачать и запустить вредонос удалённо; сами названия этих трёх библиотек источник не приводит, указывая только их функции.

Ещё один фактор распространения, механика Cargo вокруг отзыва версий. Владелец аккаунта отозвал старые релизы arrayref, с 0.3.5 по 0.3.9; когда версия отозвана (в терминологии Cargo, yanked), Cargo печатает предупреждение «советуем обновиться до версии, которая не была отозвана», а единственной неотозванной версией и была вредоносная 0.3.10. Человек, который оформил сообщение об уязвимости в RustSec, пишет, что вышел на вредоносный крейт именно через это предупреждение. Зависимость на proc-macro1 в манифесте arrayref указана как 1.0.107, а в системе версионирования Cargo такая запись по умолчанию означает caret-диапазон, при всего двух когда-либо опубликованных версиях proc-macro1 (1.0.106 и 1.0.107) это неизбежно приводит именно к вредоносной 1.0.107. Масштаб потенциального поражения велик: у arrayref порядка 245 млн загрузок за всё время (точнее, 244 989 384 на момент публикации материала), из них около 152 млн приходится на чистую версию 0.3.9; источник отдельно уточняет, что это показатель распространённости крейта, а не число реально пострадавших сборок. arrayref, глубокая транзитивная зависимость: он используется через tiny-skia, sctk-adwaita и winit, а значит затрагивает практически весь GUI-код на основе egui, eframe и iced.

Технически сборочный скрипт proc-macro1 1.0.107 хранит адрес управляющего сервера в виде фрагментов, закодированных через base64, и собирает их обратно уже во время сборки, так что сама строка адреса не видна в исходном коде. После декодирования получается хост для загрузки полезной нагрузки, hxxps://23[.]254[.]165[.]112:9089/ (адрес намеренно записан в «обезвреженном» виде, чтобы его нельзя было случайно открыть как ссылку), и адрес управления (C2) 23[.]254[.]165[.]112:443. Загрузка идёт через TLS-соединение, которое принимает вообще любой сертификат: используемый верификатор AcceptAll (реализация типажа ServerCertVerifier из библиотеки rustls) возвращает успех на любой проверке сертификата и подписи, так что самоподписанный сертификат на голом IP-адресе проходит без вопросов. Скрипт выбирает конкретный бинарник по операционной системе и архитектуре, поддерживаются четыре целевые платформы (какие именно, источник не называет), для прочих сборка просто прерывается. Загрузка и запуск происходят в самом начале функции main, ещё до флагов условной сборки (feature gates) и настоящей логики proc-macro2, которая идёт следом, никакой проверки окружения или отключающего флага нет, так что на поддерживаемой платформе вредонос запускается при каждой сборке. На Unix-системах скрипт записывает скачанные байты в /tmp/rust-setup, помечает файл исполняемым и запускает его, не дожидаясь завершения, передавая адрес C2 первым аргументом, а весь вывод перенаправляет в null. На Windows скачанные байты, это PowerShell-скрипт: сборочный скрипт сохраняет его в %TEMP%\rust-setup.ps1 и запускает через VBScript-лаунчер под wscript.exe, скрытно; по комментарию в самом исходном коде, именно запуск через wscript позволяет процессу вырваться из-под контроля job object в Windows, механизма, которым Cargo обычно останавливает дочерние процессы вместе с самой сборкой, так что PowerShell продолжает работать и после того, как сборка закончилась. Финальный вызов std::mem::forget «отпускает» дескриптор дочернего процесса wscript, чтобы его деструктор так и не сработал: вместе это полностью отвязывает вредонос от процесса Cargo, и он продолжает работу, не блокируя саму сборку.

Ключевые факты

  • 20 августа 2026 года на crates.io появилась вредоносная версия 0.3.10 крейта arrayref (около 245 млн загрузок за всё время, из них порядка 152 млн, у чистой версии 0.3.9); вредоносные версии crates.io уже удалила.
  • Версия 0.3.10 добавляла в манифест одну новую строку, зависимость от крейта proc-macro1, чей сборочный скрипт при компиляции скачивает и запускает вредоносный бинарник; сам код arrayref (четыре макроса) не менялся и вредоноса не содержит.
  • Аккаунт мейнтейнера arrayref, droundy, по всей видимости, был взломан, репозитории arrayref, append-only-vec и весь аккаунт droundy на GitHub отвечают 404; proc-macro1 опубликован с отдельного аккаунта dtolney, похожего по написанию на настоящий аккаунт dtolnay (Дэвид Толнай), с подделанными метаданными авторства.
  • Внутри proc-macro1 лежит фактически код настоящей библиотеки proc-macro2 с переименованием, поэтому проекты продолжали собираться без ошибок, пока в фоне выполнялся вредоносный сборочный скрипт.
  • Старые версии arrayref 0.3.5, 0.3.9 были отозваны, из-за чего Cargo советовал перейти на «неотозванную» версию, то есть на вредоносную 0.3.10; сама полезная нагрузка качается через TLS-соединение, принимающее любой сертификат, и запускается в процессе, отсоединённом от Cargo (на Unix, /tmp/rust-setup, на Windows, через PowerShell и VBScript-лаунчер wscript.exe), не блокируя саму сборку.

Почему это важно

Это не рядовой вредоносный пакет в реестре: вредонос запускается уже на этапе сборки (cargo build), то есть срабатывает у любого, кто просто компилирует проект, а не только у тех, кто запускает готовый бинарник. Атака сочетает три приёма сразу: типосквоттинг названия (proc-macro1 вместо настоящего proc-macro2), подделку личности известного в экосистеме Rust аккаунта (dtolney мимикрирует под настоящий dtolnay, метаданные подделывают авторство под Дэвида Толная) и маскировку, внутри вредоносного крейта лежит рабочая копия настоящей библиотеки, поэтому сборка ничего не ломает и не вызывает подозрений при обычном просмотре. При этом arrayref, не нишевая библиотека: у него около 245 млн загрузок за всё время, и он глубоко сидит в зависимостях популярных Rust-крейтов для GUI.

Кому это важно

В первую очередь, Rust-разработчикам и мейнтейнерам, у кого arrayref стоит в дереве зависимостей: он используется как транзитивная зависимость через tiny-skia, sctk-adwaita и winit, а значит затрагивает почти весь GUI-код на базе egui, eframe и iced. Важно это и всем, кто вообще полагается на публичные пакетные реестры вроде crates.io (а по аналогии, npm, PyPI): история показывает конкретно работающий сценарий атаки на цепочку поставок (supply chain), актуальный для инженеров по безопасности и специалистов по DevOps и CI, которые проверяют зависимости и настраивают изолированные окружения сборки.

Как это применить

Прямых инструкций по устранению источник не даёт, но из описанного механизма следуют конкретные проверки. Стоит посмотреть дерево зависимостей своего проекта (cargo tree) на предмет пакета proc-macro1, если он там есть, это и есть индикатор компрометации. Тем, кто видел предупреждение Cargo про отозванную версию arrayref, не стоит слепо переходить на предложенную «неотозванную» версию, безопаснее явно зафиксировать в Cargo.lock последнюю известную чистую версию 0.3.9, а не полагаться на автоматическое обновление до последней доступной. Поскольку вредонос выполняется именно в сборочном скрипте, а не в рантайме готовой программы, обычная песочница для итогового бинарника здесь не поможет, нужна изоляция самого процесса сборки, например сборка в контейнере без сетевого доступа и без учётных данных CI. Если сборка с версией 0.3.10 уже прошла на каком-то узле, этот узел стоит считать потенциально скомпрометированным и проверить на признаки из источника: файл /tmp/rust-setup на Unix, файл rust-setup.ps1 и связанный VBScript-лаунчер в %TEMP% на Windows, а также сетевые соединения к адресу 23[.]254[.]165[.]112.

Можно ли доверять

Материал, подробный технический разбор от команды SafeDep (safedep.io), компании, которая сама продвигает инструменты для проверки безопасности open-source-пакетов, то есть у публикации есть и коммерческий интерес. При этом разбор предметный: авторы называют конкретные версии, цитируют предупреждение Cargo и разбирают сборочный скрипт по шагам, вплоть до техники обхода job object в Windows, это заметно основательнее, чем голословное сообщение о «вредоносном пакете». Есть и пробелы: в материале обещан раздел с SHA256-хешами удалённых версий крейта, но сам хеш в тексте не отображается; не названы поимённо ни три служебные библиотеки, добавленные в зависимости для сборки (описаны только их функции, base64, TLS, HTTP-клиент), ни четыре поддерживаемые целевые платформы; не указано, кто и как именно скомпрометировал аккаунт droundy, и не назван по имени человек, подавший сообщение в RustSec. Независимого подтверждения от crates.io или кого-то ещё в тексте нет, источник единственный, хотя и весьма детальный.

Риски и подводные камни

Главный риск, момент срабатывания: вредонос запускается уже при компиляции, то есть на машине разработчика или в CI/CD ещё до того, как готовая программа хоть раз запущена, зачастую с теми же правами, что и сама сборка. Отсоединение процесса от Cargo (через wscript и job object на Windows, через фоновый запуск с перенаправлением вывода в null на Unix) означает, что остановка или сбой самой сборки не останавливает уже запущенный вредонос, он продолжает работать независимо от результата cargo build. Атака кросс-платформенная: отдельные ветки кода нацелены и на Unix, и на Windows. Маскировка под настоящий proc-macro2 (тот же код, только переименованный) означает, что беглый просмотр исходников ничего не покажет, вредоносная часть целиком лежит в сборочном скрипте, а не в самой библиотеке. Наконец, отзыв старых версий как приём, механизм, который должен защищать пользователей от плохих релизов, здесь наоборот подтолкнул их к единственной оставшейся вредоносной версии; то, что crates.io уже удалила вредоносные версии, закрывает риск для новых установок, но не отменяет последствий для тех, кто уже успел собрать проект с 0.3.10 до удаления.

«Советуем обновиться до версии, которая не была отозвана»

— предупреждение Cargo, которое видят разработчики при использовании отозванной (yanked) версии крейта