Колонки

Pluto Security рассказала, как загрузка с trust_remote_code превращается во вредоносное ПО

Приём, лежащий в основе этой кампании, — не эксплойт. Это функция. Анализ Pluto Security, посвящённый операции по скрытому майнингу криптовалюты на Hugging Face Hub, показывает, что злоумышленнику вообще не пришлось ничего взламывать. Библиотека загрузки моделей выполняет заражающий машину код на Python именно так, как и задумано.

Pluto Security рассказала, как загрузка с trust_remote_code превращается во вредоносное ПО

Три двери, одна открыта

Загрузка открытой модели может привести к выполнению кода через три разные двери. Первая — парсер, читающий бинарный формат, например GGUF. Именно эта поверхность атаки стоит за CVE-2024-34359, когда шаблон чата, хранившийся в файле GGUF, выполнял код через llama-cpp-python.

Вторая — формат сериализации: веса в форматах pickle, Keras и SavedModel могут содержать исполняемую логику. Это самый известный риск в безопасности машинного обучения, и для него существует больше всего инструментов. Третья — путь импорта Python, trust_remote_code=True, и именно через эту дверь действует данная кампания.

Фреймворки для развёртывания моделей уже спотыкались об эту же дверь. CVE-2026-22807 показала, что vLLM (с 0.10.1 до 0.14.0) загружал модули auto_map, не проверяя настройку trust_remote_code. Вредоносный репозиторий модели мог выполнить код при запуске сервера. Проблема исправлена в версии 0.14.0.

Цепочка выполнения

Разработчик вызывает from_pretrained() с trust_remote_code=True для того, что выглядит как небольшая модель Qwen2. В файле config.json репозитория через auto_map указаны модуль и класс, например modeling_evil2.EvilModel. Transformers скачивает указанный файл в свой кеш модулей и импортирует его.

В get_class_in_module() функция exec_module() выполняет код верхнего уровня файла до того, как getattr() вернёт запрошенный класс. Любой вызов, размещённый рядом с определением класса, срабатывает первым. В типичной сборке MENXIANGYU/qwen2-tiny импорт модуля доходит до функции _boot() раньше определения класса.

Лежащий рядом model.safetensors весит всего 42 байта и не содержит тензоров. Атака не использует ни отравленных весов, ни эксплойта pickle.

Почему сканеры молчат

Hugging Face действительно сканирует файлы репозиториев. Согласно документации, каждый файл размером меньше 2 ГБ проверяется антивирусным сканером ClamAV при каждом коммите. ClamAV сверяет файлы с сигнатурами известных вредоносных программ. Форматы весов проверяют сканеры партнёров. В документации по pickle сказано, что сканирование «не даёт стопроцентной гарантии» и что проверка безопасности — ответственность пользователя.

Обычный файл Python, указанный в auto_map, не является ни известной сигнатурой, ни сериализованной моделью. В результате никто не анализирует, что он делает. В проверках Pluto Security файл modeling_evil2.py из qwen2-tiny был признан безопасным всеми запущенными сканерами. Антивирус ничего не нашёл, а JFrog сообщил, что это не модель машинного обучения.

То же самое касалось и файлов первой волны. Формат safetensors, созданный для устранения риска выполнения кода, фигурирует в этой кампании лишь как 42-байтовая заглушка.

Первый этап: сначала — плацдарм

Полезная нагрузка записывает /tmp/.cch/svc.py — сторожевой процесс (watchdog), работающий отдельно. Его перезапускают хуки, дописанные в /root/.bashrc, /root/.profile и ~/.bashrc. Используя интерпретатор самого загрузчика модели, он каждые 120 секунд перезапускает майнер, если тот отсутствует.

Три сборки — qwen2-tiny-5, -6 и -9 — добавляют нулевой шаг. Отдельный процесс подключает оболочку к 212.32.253.3 на порт 443 или 9443 и повторяет попытку после каждого разрыва. Успешное подключение даёт злоумышленнику интерактивную оболочку с доступом к файлам и учётным данным скомпрометированного процесса.

Второй этап: майнер под видом рабочего потока ядра

Затем наступает очередь бинарного файла майнера syscore.bin. Полезная нагрузка проверяет скачанный снимок модели, затем /tmp/.cch/sync, оставшийся от предыдущего заражения. В противном случае она загружает бинарник из репозитория Hugging Face или с зеркала.

Если требуется загрузка, клиент отключает настроенные на хосте прокси. Если сеть допускает прямой исходящий трафик, загрузка минует инспекцию и журналы этого прокси. Обойти сеть, где использование прокси обязательно, она не может.

При первом запуске полезная нагрузка копирует бинарник в файловый дескриптор в памяти с помощью memfd_create. Затем запускает его через fexecve под именем [kworker/u20:1]. При этом первом запуске на диске не остаётся исполняемого файла, который мог бы проверить файловый антивирус или EDR. В списке процессов он выглядит как рабочий поток ядра.

Но маскировка себя выдаёт: в отличие от настоящего потока ядра, у процесса есть аргументы пользовательского пространства, включая логин для майнинга. Резервная копия остаётся в /tmp/.cch/sync для перезапусков сторожевым процессом.

Бинарник размером 40,6 МБ упакован UPX. Два маркера UPX! в метаданных упаковки, по смещениям 0x000000ec и 0x026be650, заменены на PEK!, поэтому upx -d отвергает файл как не упакованный UPX. Встроенный загрузчик всё равно распаковывает и запускает майнер.

Pluto Security восстановила оба маркера и распаковала файл, обнаружив peakminer. Это тот же клиент, о котором сообщали активные воркеры на кошельке первой волны. Загрузчик при запуске передаёт адреса пулов и логин krxXJ2PKQW.worker. Учётная запись оператора в самом майнере не зашита.

Третий этап: отчёты и зачистка конкурентов

Отчёты о ходе работы помещаются в локальный реестр моделей Xinference под видом названий моделей: qwen2-tiny-, за которым следует статус в кодировке base32. Оператор, имеющий доступ к открытому API, может их расшифровать.

Отметки варьируются от B (запуск выполнен) и P (бинарник на месте) до OK и OK2 (работа через путь в памяти или резервный путь на диске), DLN и NM (сбой получения или запуска), OK3 (сторожевой процесс восстановил майнинг) и H — сигнала активности с данными о загрузке GPU каждый пятый цикл сторожевого процесса.

Запустив собственный майнер, полезная нагрузка удаляет регистрации конкурирующих моделей. Она вычищает их команды запуска и завершает соответствующие процессы. Более ранние сборки искали в /proc/*/cmdline 13 известных названий конкурирующих майнеров.

Что это значит для инженеров

Кража вычислительных ресурсов для майнинга — видимая часть ущерба. Долгосрочный ущерб — это доступ. SSH-ключи, хуки в профилях оболочки, сторожевой процесс и обратные оболочки переживают майнер. Pluto Security отмечает, что завершение майнера или удаление модели не устраняет закрепление в системе.

Компания рекомендует проверять файлы .py, на которые указывает auto_map, прежде чем устанавливать флаг, и фиксировать проверенную ревизию. Она также советует запускать недоверенный код с ограниченными привилегиями, ограниченным доступом к сети и без производственных секретов, а также требовать аутентификацию для API Xinference. Чистый результат сканирования сам по себе — не повод доверять репозиторию.

Опубликовано 29 октября 2026 - 05:00 Наверх