3 часа, 3.4 миллиона загрузок в день: как атака на LiteLLM скомпрометировала цепочку поставок Python
24 марта злоумышленники внедрили бэкдор в популярный Python-пакет LiteLLM через скомпрометированный сканер безопасности Trivy. Разбираем трёхступенчатую атаку, случайную fork-бомбу и последствия для всей экосистемы.

3,4 миллиона установок в сутки. Именно столько раз пакет LiteLLM — универсальная обёртка над API десятков LLM-провайдеров — скачивается с PyPI каждый день. 24 марта 2026 года две его версии содержали полноценный бэкдор: сборщик учётных данных, шифровальщик и червь для Kubernetes. Вредоносные версии провисели на PyPI около трёх часов. Обнаружил их разработчик, у которого просто зависла машина.
Как всё началось: отравленный сканер
Чтобы понять атаку на LiteLLM, нужно отмотать на пять дней назад. 19 марта группировка TeamPCP — она же PCPcat, Persy_PCP, ShellForce, DeadCatx3 — перезаписала теги GitHub Action популярного сканера безопасности Trivy версии 0.69.4. Вместо легитимного кода тег стал указывать на вредоносный релиз. Это классический приём: GitHub Actions ссылаются на теги, а теги — мутабельны. Перезаписал тег — и тысячи CI/CD-пайплайнов начинают выполнять твой код.
23 марта атакующие скомпрометировали ещё один инструмент — Checkmarx KICS, и зарегистрировали несколько доменов, включая models.litellm.cloud. Домен мимикрировал под легитимную инфраструктуру LiteLLM и служил точкой эксфильтрации украденных данных.
А утром 24 марта всё сошлось. Скомпрометированный Trivy в CI/CD-пайплайне LiteLLM позволил злоумышленникам получить PyPI-токены мейнтейнера проекта. В 10:39 UTC на PyPI появилась версия 1.82.7 с вредоносным кодом. Через тринадцать минут — версия 1.82.8 с усовершенствованным механизмом доставки.
Случайная fork-бомба как сигнал тревоги
Ирония в том, что атаку раскрыла ошибка самих атакующих. Каллум Макмахон из FutureSearch тестировал MCP-плагин для Cursor, который подтягивал LiteLLM как зависимость. Машина намертво встала — оперативная память исчерпалась за секунды.
Когда Макмахон разобрался, что произошло, он обнаружил файл litellm_init.pth размером 34 628 байт в директории site-packages. Файл был закодирован двойным base64, а главное — использовал механизм .pth, который Python выполняет автоматически при каждом запуске интерпретатора. Каждый запуск Python порождал подпроцесс, который сам запускал Python, который снова срабатывал на .pth — классическая fork-бомба. Непреднамеренная, но весьма заметная.
В 11:48 UTC Макмахон открыл issue на GitHub. К 13:38 UTC PyPI поставил весь пакет LiteLLM на карантин. К 15:27 скомпрометированные версии были удалены, а пакет разблокирован.
Два механизма доставки
Злоумышленники использовали два разных подхода к внедрению полезной нагрузки, и эволюция между ними говорит о многом.
Версия 1.82.7 содержала base64-закодированный payload прямо в файле litellm/proxy/proxy_server.py. Код выполнялся при импорте модуля — то есть только когда разработчик явно использовал LiteLLM в своём приложении.
Версия 1.82.8, выпущенная всего через 13 минут, перешла на механизм .pth-файлов. Это принципиально другой уровень: файл litellm_init.pth срабатывает при каждом запуске Python-интерпретатора в данном окружении. Не нужно импортировать LiteLLM. Не нужно запускать приложение. Достаточно выполнить pip install чего угодно — и бэкдор активирован. Даже сам pip запускает Python, который обрабатывает .pth-файлы.
Трёхступенчатая полезная нагрузка
Внутри закодированного payload скрывалась продуманная трёхступенчатая система.
Первая ступень — сбор данных:
- SSH-ключи (
~/.ssh/) - Файлы
.envсо всеми переменными окружения - API-ключи всех основных провайдеров
- Учётные данные облачных платформ (AWS, GCP, Azure)
- Конфигурации Docker и Kubernetes
- Криптовалютные кошельки
Вторая ступень — шифрование и эксфильтрация. Собранные данные шифровались AES-256 с обёрткой RSA-4096 и отправлялись через curl на models.litellm.cloud. Выбор домена не случаен — трафик к нему не вызывал бы подозрений у систем мониторинга, привыкших видеть обращения к инфраструктуре LiteLLM.
Третья ступень — закрепление. Бэкдор устанавливался по адресу ~/.config/sysmon/sysmon.py, маскируясь под системный мониторинг. На Linux-системах создавался systemd-сервис для автозапуска. Но самое агрессивное — модуль для Kubernetes: при наличии доступа к кластеру вредонос разворачивал привилегированные поды, распространяясь по всей инфраструктуре.
Подавление раскрытия
Когда Макмахон открыл issue на GitHub, произошло нечто необычное. В течение 102 секунд появилось 88 комментариев от 73 разных аккаунтов — очевидно ботов — забивающих тред шумом. Затем issue был закрыт с использованием скомпрометированного аккаунта мейнтейнера LiteLLM. Злоумышленники пытались выиграть время: каждая лишняя минута — это тысячи дополнительных установок скомпрометированного пакета.
Эта тактика — активное подавление раскрытия — выделяет атаку на фоне обычных supply-chain-инцидентов. Обычно злоумышленники надеются, что никто не заметит. Здесь они были готовы к тому, что заметят, и имели план противодействия.
Девятая фаза кампании
По данным Snyk, атака на LiteLLM — это фаза 09 продолжающейся кампании TeamPCP. Одни и те же RSA-ключи использовались в операциях с Trivy, Checkmarx KICS и LiteLLM. Группировка целенаправленно атакует инструменты безопасности и DevOps-инфраструктуру, понимая, что именно через них можно добраться до максимального числа жертв.
Логика элегантна в своей жестокости: сканер безопасности — последнее место, где разработчик ожидает найти вредоносный код. Доверие к инструментам защиты становится вектором атаки.
Масштаб последствий
В течение нескольких часов после раскрытия экстренные pull-реквесты для обновления или блокировки LiteLLM появились в крупных проектах.
Затронутые проекты:
- DSPy (Stanford NLP)
- MLflow (Databricks)
- OpenHands (бывший OpenDevin)
- CrewAI
- Langwatch
- Arize Phoenix
Каждый из этих проектов включает LiteLLM как зависимость, а значит, их пользователи тоже могли установить скомпрометированные версии в трёхчасовом окне.
Точное число пострадавших установить сложно. При 3,4 миллиона загрузок в день и трёхчасовом окне простая арифметика даёт порядка 425 тысяч потенциальных установок. Но реальное число, вероятно, значительно меньше — далеко не все установки происходят равномерно, а многие компании используют внутренние зеркала PyPI с задержкой.
Что это означает для экосистемы
Атака на LiteLLM обнажает фундаментальную проблему современной разработки: мы строим системы безопасности из компонентов, каждый из которых может быть скомпрометирован. Trivy — инструмент для поиска уязвимостей — сам стал вектором атаки. GitHub Actions с мутабельными тегами — механизм, который невозможно надёжно верифицировать без привязки к конкретному SHA-хешу коммита. PyPI-токены без ротации и без аппаратных ключей — точка отказа, через которую один скомпрометированный пайплайн превращается в миллионы скомпрометированных установок.
Для команд, работающих с LLM-инфраструктурой, последствия практические. Если в окне между 10:39 и 13:38 UTC 24 марта вы устанавливали LiteLLM или любой пакет, зависящий от него, необходимо проверить наличие файла litellm_init.pth в site-packages, директории ~/.config/sysmon/, а также ротировать все ключи и токены, которые были доступны в окружении.
Механизм .pth-файлов — отдельная тема для беспокойства. Мало кто из разработчиков знает, что Python автоматически выполняет код из таких файлов при каждом запуске. Это не баг — это задокументированная функция для настройки путей импорта. Но в контексте безопасности это означает, что один вредоносный пакет может получить выполнение кода без единого import со стороны жертвы. PyPI пока не ограничивает содержание .pth-файлов в пакетах.
Выводы
Три часа — это ничтожно мало и одновременно катастрофически много. Достаточно, чтобы тысячи разработчиков и CI/CD-систем успели установить скомпрометированный пакет. Недостаточно, чтобы большинство из них это заметили.
Атака TeamPCP показала, что supply-chain-атаки вышли на новый уровень зрелости. Это не одиночка, подменяющий имя пакета. Это координированная кампания с разведкой, несколькими векторами атаки, механизмами подавления раскрытия и планом эксфильтрации. Девятая фаза подразумевает, что первые восемь тоже существовали — и не все из них могли быть обнаружены.
Практический вывод один: привязывайте зависимости к конкретным хешам, используйте lockfile, проверяйте GitHub Actions по SHA коммита, а не по тегу. И если ваша машина внезапно начала тормозить после pip install — не перезагружайте. Разбирайтесь.


