К содержимому
Завалищев
База знаний
Безопасность 7 мин чтения

Анатомия .pth-бэкдора в LiteLLM: как устроена атака на AI supply chain и как аудировать MCP-серверы

Детальный технический разбор .pth-механизма, использованного в компрометации LiteLLM: как файл в 34 КБ исполняется при каждом запуске Python, крадёт credentials и разворачивает червя в Kubernetes. Плюс — Go-сканер для проверки MCP-серверов на аналогичные паттерны.

Вчера я разобрал сам инцидент с LiteLLM — компрометацию PyPI-пакета группой TeamPCP. Сегодня — вскрытие: как именно работает .pth-механизм, почему это слепая зона Python-экосистемы и что делать, если вы используете MCP-серверы на Python.

Что такое .pth-файл и почему это оружие

Python-модуль site обрабатывает файлы .pth в site-packages/ при каждом запуске интерпретатора. Их основное назначение — добавлять пути в sys.path. Но начиная с Python 3.5 любая строка, начинающаяся с import, исполняется как код.

Это не баг — это задокументированная фича. coverage.py использует .pth для перехвата запуска до пользовательского кода. Проблема в том, что атакующие используют тот же механизм.

Вот что это значит на практике:

  • Не нужен import. Файл исполняется даже если ваш код не импортирует пакет.
  • Срабатывает всегда. python -c "print(1)", pip install something, запуск pytest, language server в IDE — всё триггерит .pth.
  • Проходит проверки целостности. Файл прописан в RECORD колеса, SHA-256 совпадает. pip считает его легитимным.

Мейнтейнеры CPython знают об этой проблеме (issue #113659), но патча нет. MITRE присвоила технике T1546.018 (Python Startup Hooks).

Два вектора: v1.82.7 vs v1.82.8

TeamPCP использовали два разных механизма доставки в двух версиях, выпущенных с разницей в 13 минут.

v1.82.7 — инъекция в исходный код

Base64-закодированный payload вставлен в litellm/proxy/proxy_server.py на строке 128, между двумя легитимными блоками кода. Тройная вложенность: base64 в proxy_server.py → оркестратор → ещё один base64 с actual harvester. Срабатывает при import litellm.proxy — стандартный путь для proxy-режима.

v1.82.8 — .pth-файл (эскалация)

Файл litellm_init.pth (34 628 байт) в site-packages/. Двойное base64-кодирование. Не требует импорта — исполняется при любом запуске Python в этом окружении.

Критический баг в малвари: .pth-лаунчер запускает дочерний Python-процесс через subprocess.Popen. Дочерний процесс снова триггерит .pth. Результат — экспоненциальная fork-бомба. Именно она привела к обнаружению: у исследователя из FutureSearch машина зависла из-за исчерпания RAM.

Трёхступенчатый payload

Stage 1: Сбор credentials

Скрипт собирает всё, до чего может дотянуться: SSH-ключи из ~/.ssh/, AWS credentials/config и IMDS-запросы (полная поддержка IMDSv2), GCP ADC и Azure-токены, Kubernetes kubeconfig и service account токены, OPENAI_API_KEY / ANTHROPIC_API_KEY / HUGGINGFACE_TOKEN из env и .env-файлов, Jenkins/Travis CI/Terraform конфиги, кошельки Bitcoin / Ethereum / Solana / Cardano / Monero, git credentials, shell history, Slack/Discord webhook-токены, /etc/shadow.

Stage 2: Шифрование и эксфильтрация

Генерируется 32-байтный AES-256 сессионный ключ. Данные шифруются AES-256-CBC (PBKDF2). Сессионный ключ шифруется RSA-4096 (OAEP) — публичный ключ зашит в payload. Всё пакуется в tpcp.tar.gz и отправляется POST на models.litellm.cloud через curl.

Домен models.litellm.cloud зарегистрирован 23 марта — за день до атаки. RSA-ключ идентичен тому, что использовался в компрометации Trivy и KICS. Это главная техническая связь атрибуции.

Stage 3: Persistence и lateral movement

Локальная persistence: бэкдор ~/.config/sysmon/sysmon.py, systemd-юнит sysmon.service с описанием «System Telemetry Service», опрос C2 checkmarx.zone/raw каждые 5 минут.

Kubernetes-червь: если на хосте есть service account токен, малварь читает все секреты во всех namespace'ах, деплоит привилегированный alpine:latest под на каждый node в kube-system, монтирует хостовую файловую систему и устанавливает sysmon-бэкдор на каждую ноду.

Kill chain

.pth auto-execute при запуске Python → subprocess.Popen (base64-оркестратор) → декодирование + запуск credential harvester → AES-256-CBC + RSA-4096 шифрование → POST на models.litellm.cloud → проверка K8s service account token → чтение всех cluster secrets → деплой privileged pods → persistence через sysmon.py + systemd → C2: опрос checkmarx.zone/raw.

Почему MCP-серверы — следующая цель

MCP (Model Context Protocol) — стандарт подключения инструментов к AI-агентам. Типичная конфигурация: указать Python-окружение и команду запуска, часто с широкими правами на файловую систему.

Что это значит: MCP-сервер тянет зависимости, LiteLLM — одна из самых частых транзитивных зависимостей в AI-стеке. При запуске MCP-сервера запускается Python-интерпретатор — триггерятся все .pth в окружении. MCP-серверы работают с credentials для инструментов: GitHub, Slack, базы данных, cloud API.

Исследователь из FutureSearch обнаружил атаку именно так — MCP-плагин в Cursor подтянул litellm как транзитивную зависимость.

ToolTrust Scanner: Go-сканер для аудита MCP

После инцидента появился ToolTrust Scanner — open-source Go-инструмент для аудита MCP-серверов. Работает полностью офлайн.

Что сканирует:

  • Prompt injection и tool poisoning — скрытые инструкции в описаниях инструментов
  • Избыточные разрешения — exec, network, db, fs
  • Supply chain CVE — включая LiteLLM 1.82.7/8
  • Privilege escalation — паттерны arbitrary code execution
  • Тайпосквоттинг и tool shadowing — подмена инструментов похожими именами

При обнаружении LiteLLM 1.82.7/8 выдаёт CRITICAL Grade F и блокирует установку. Есть веб-интерфейс на tooltrust.dev.

Практический чеклист: что проверить прямо сейчас

1. Найти скомпрометированные версии

Выполните pip show litellm | grep -E "^Version:" для всех окружений. Проверьте uv-кеши: find ~/.cache/uv -name "litellm_init.pth".

2. Найти .pth с исполняемым кодом в любом окружении

Используйте python3 для обхода site.getsitepackages() и поиска строк, начинающихся с "import " — в обычном окружении таких строк быть не должно.

3. Проверить persistence-артефакты

Ищите ~/.config/sysmon/sysmon.py, ~/.config/systemd/user/sysmon.service, поды node-setup-* в kube-system.

4. Ротация credentials

Если была установлена 1.82.7 или 1.82.8 — ротировать все ключи: SSH, AWS/GCP/Azure, все API-ключи LLM-провайдеров, Kubernetes service account токены.

Системные выводы

Python .pth — архитектурная уязвимость. Механизм исполнения кода при старте интерпретатора без явного импорта — идеальный вектор для supply chain атак. CPython-команда это признаёт, но не фиксит.

AI supply chain — жирная мишень. LiteLLM по дизайну хранит ключи от десятков провайдеров. Компрометация одного пакета = доступ ко всей AI-инфраструктуре.

MCP требует security-прокси. Запускать MCP-серверы с полным доступом к окружению — это как давать root каждому плагину. Инструменты вроде ToolTrust — первый шаг, но экосистеме нужен стандарт аудита.

Транзитивные зависимости — главный вектор. Разработчик из FutureSearch не устанавливал LiteLLM — его подтянул MCP-плагин в Cursor. pip install something — это всегда лотерея из сотен пакетов.

TeamPCP систематически движется по цепочке доверия: сначала security-сканеры (Trivy, Checkmarx), теперь AI-инфраструктура. Следующая цель — предсказуема.