LiteLLM 1.107.0 на PyPI оказался вредоносным: как проверить свои окружения

21 октября 2026 года команда LiteLLM сообщила в своём блоге, что её релизный конвейер взломали, и на PyPI и в Docker-реестры ушла версия 1.107.0 с вредоносом. Он крадёт учётные данные и умеет подтягивать с сервера злоумышленника дополнительный код. LiteLLM это Python SDK и шлюз, через который приложения ходят к моделям разных провайдеров по одному API. По данным BleepingComputer, у пакета около 92 млн загрузок в месяц.

Если у вас на сервере или в CI стоит LiteLLM без жёстко закреплённой версии, или контейнер берётся по тегу latest, стоит проверить, что именно подтянулось 21 октября. Опасна эта версия не только для тех, кто реально пользуется шлюзом: вредонос срабатывал при любом запуске Python в заражённом окружении.

Что было заражено и когда

По словам LiteLLM, затронуты такие артефакты (время московское):

  • PyPI: все семь wheel-пакетов litellm==1.107.0, лежали с 14:39 до 16:23;
  • Docker Hub: теги v1.107.0, latest, main-latest и main-stable указывали на заражённый образ с 14:46 до 17:20;
  • GHCR: те же теги у образов litellm, litellm-database и litellm-non_root с 14:46 до 16:51.

Исходники по тегу v1.107.0 на GitHub чистые: заражённые пакеты собрали из подставного репозитория Berri-AI/litellm, похожего на настоящий BerriAI/litellm. Предыдущие версии, по словам команды, безопасны, а клиенты закрытой корпоративной сборки не пострадали. Заражённый релиз с PyPI уже убран, а 24 октября там вышла версия 1.106.3.

Почему хватало просто запустить Python

Сам код LiteLLM злоумышленник не трогал. В пакет добавили три файла: cook.pth, cook.py и sitecustomize.py. При установке они ложатся прямо в каталог site-packages, а не внутрь папки litellm. Python при каждом старте обрабатывает файлы .pth и выполняет в них строки с import, а потом пытается импортировать модуль sitecustomize. В cook.pth была одна строка:

import cook

Как разобрали в Microsoft, cook.py распаковывает спрятанный в base64 и zlib сборщик и запускает его в фоновом потоке. Импортировать litellm не нужно: в одном из случаев, которые видела Microsoft, сборщик сработал на Windows, когда человек ставил через pip совсем другую зависимость. Отсюда главное правило: в подозрительном окружении не запускать ни python, ни pip.

Сборщик искал всё, до чего мог дотянуться:

  • ключи и конфиги AWS, Azure, Google Cloud, Kubernetes и Docker, а также временные ключи из метаданных облака;
  • токены Git, GitHub, npm, PyPI и CI/CD, файлы .env;
  • SSH-, GPG- и TLS-ключи;
  • настройки и токены ИИ-инструментов, IDE и MCP;
  • сохранённые пароли, куки и историю браузеров, историю shell;
  • переменные окружения, на Linux ещё и чужих процессов через /proc;
  • ключи провайдеров и настройки моделей из базы прокси LiteLLM.

Собранное шифровалось (AES-256 плюс RSA-4096) и отправлялось POST-запросом на www.litellm.dev/healthcheck, домен под видом настоящего проекта. После успешной отправки появлялся файл-метка /tmp/.pc_once_72129118c59f5636c0bb.

Второй файл, sitecustomize.py, работал на Linux и macOS. Он запускал фоновый процесс с блокировкой /tmp/remote_code_loader.lock, который каждый час читал адрес сервера из файла на GitHub и мог скачать и выполнить оттуда новый код на Python. По данным Microsoft, файл с адресом был пустым, и второй этап никто не наблюдал, но злоумышленник мог включить его в любой момент.

Как проверить сервер

Проверять снаружи, обычными утилитами shell, не запуская интерпретатор из подозрительного окружения. Поиск следов в файловой системе и временном каталоге:

find / -xdev \( -name 'cook.pth' -o -name 'cook.py' -o -name 'litellm-1.107.0.dist-info' \) 2>/dev/null
ls -la /tmp/remote_code_loader.lock /tmp/.pc_once_72129118c59f5636c0bb 2>/dev/null
ps aux | grep -i '[r]emote_code_loader\|[c]ook'

Версию в lock-файлах проектов и кэшах pip и uv тоже стоит поискать:

grep -rnE 'litellm(==|-)1\.107\.0' --include='*.txt' --include='*.lock' --include='*.toml' ~ /srv /opt 2>/dev/null
grep -rn -A1 'name = "litellm"' --include='*.lock' ~ /srv /opt 2>/dev/null | grep '1.107.0'
find ~/.cache/pip ~/.cache/uv -iname '*litellm*1.107.0*' 2>/dev/null

Для Docker сравните дайджест образа с заражённым sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e:

docker images --digests | grep -i litellm
docker ps -a --format '{{.Image}} {{.CreatedAt}} {{.Names}}' | grep -i litellm

Как предупреждает LiteLLM, найденный файл подтверждает заражение, а пустой результат чистоту не доказывает: контейнеры и очистка /tmp могли всё стереть. Сам по себе sitecustomize.py ничего не значит, он бывает и в обычных установках Python. Microsoft советует сверять хэши файлов, они есть в её разборе.

Что делать, если 1.107.0 стояла

Порядок действий из поста LiteLLM:

  • не начинать с pip install: любой запуск Python в этом окружении снова выполнит вредонос;
  • из shell удалить cook.pth, cook.py, sitecustomize.py и их .pyc в __pycache__ из каждого затронутого site-packages;
  • убить фоновый процесс Python, который держит /tmp/remote_code_loader.lock;
  • поставить версию с явным пином на чистую сборку, а лучше пересобрать venv и образы с нуля.

Если у машины был выход в интернет, считайте, что данные ушли. Менять нужно всё, до чего хост мог дотянуться: роль IAM инстанса, ключи провайдеров в базе прокси LiteLLM, мастер-ключ и виртуальные ключи, пароль из DATABASE_URL, любые секреты из переменных окружения. Microsoft добавляет к списку SSH- и GPG-ключи, токены CI, реестров и ИИ-инструментов, а также сессии в браузере. Ключи стоит менять с заведомо чистой машины.

Как взломали релиз LiteLLM

23 октября команда выложила подробный разбор. 27 мая в публичный репозиторий LiteLLM-Labs/lite-harness случайно закоммитили конфиг с мастер-ключом от внутреннего сервиса LiteLLM Agent Platform. Коммит быстро перезаписали, но сам ключ так и не отозвали.

  • 10 октября злоумышленник зашёл с этим ключом в API сервиса и получил оттуда OAuth-токен GitHub CLI одного из владельцев организации на GitHub;
  • с этим токеном он запушил временные ветки в репозитории, и их workflow отправили секреты CI на webhook.site. Так он получил токены ещё двух мейнтейнеров;
  • 21 октября он в обход защиты веток поменял релизный скрипт, направив сборку на подставной Berri-AI/litellm. Запуск одобрил токеном второго мейнтейнера, а записи о запусках удалил токеном третьего.

Обычно для выпуска нужна была отдельная учётка, привязанная к одному ноутбуку в офисе. Но на той неделе правило ослабили, чтобы чаще выпускать исправления, и релиз могли запустить и одобрить любые два инженера. Подмену заметил сторонний разработчик LidorMachluf и через полчаса после выхода релиза завёл issue на GitHub.

Что взять себе

  • закреплять зависимости с хэшами в lock-файлах (uv.lock, pip install --require-hashes), а не ставить последнюю версию при каждой сборке;
  • в Docker указывать образ по дайджесту, а не по тегу latest;
  • не давать серверам, CI и контейнерам свободный выход в интернет, если он им не нужен;
  • отзывать утёкший ключ сразу, даже если коммит уже перезаписан;
  • не хранить в окружении приложения лишние секреты: вредонос забирает всё, что видит процесс.

Заражённый LiteLLM провисел на PyPI меньше двух часов, но этого хватило, чтобы он попал в CI, контейнеры и на рабочие машины тех, кто ставил пакет без пина версии. Если 1.107.0 у вас была, удалять один пакет мало: нужно пересобрать окружение и сменить все ключи, которые оно видело.

Источники и ссылки


Комментарии загружаются…