Pickle-десериализация в Python: риски RCE, реальные уязвимости и методы защиты

📋 Кратко

Модуль pickle в Python — удобный, но опасный инструмент сериализации. Из-за опкодов GLOBAL и REDUCE он позволяет выполнять произвольный код на сервере. В статье разбирается механика атаки через __reduce__, реальные CVE, а также методы защиты: отказ от pickle, RestrictedUnpickler, HMAC-подпись и переход на safetensors для ML-моделей.

⏱ 8 минут чтениясложность
Банка с бинарным кодом под зелёным неоном в серверной
Банка с бинарным кодом под зелёным неоном в серверной

📌 Зачем нужна сериализация и в чём проблема pickle

Сериализация — фундаментальный механизм в разработке: преобразовать объект в поток байтов для хранения в файле, передачи по сети или сохранения в кеше (Redis, Memcached). Python предлагает для этого несколько встроенных инструментов, и самый популярный из них — модуль pickle. Он умеет сериализовывать практически любые объекты Python: экземпляры классов, функции, вложенные структуры — в компактный бинарный формат. Казалось бы, идеальное решение для кеширования, очередей задач (Celery), распределённых вычислений и обмена данными между микросервисами.

Опасные опкоды GLOBAL и REDUCE в виртуальной машине pickle
Опасные опкоды GLOBAL и REDUCE в виртуальной машине pickle

Однако у этого удобства есть оборотная сторона. Официальная документация Python предупреждает прямым текстом: «The pickle module is not secure. Only unpickle data you trust.» — модуль небезопасен, доверяйте только проверенным данным. В отличие от JSON или XML, pickle не просто восстанавливает данные — он восстанавливает произвольные объекты Python, вызывая их конструкторы и методы инициализации. Это открывает прямую дорогу к удалённому выполнению кода (RCE).

Проблема усугубляется тем, что pickle используется в тысячах Python-пакетов и фреймворков — от библиотек машинного обучения (PyTorch, scikit-learn, TensorFlow) до систем очередей (Celery, RQ) и веб-фреймворков (Django). Разработчики часто не осознают, что передача pickle-данных через внешний интерфейс равносильна предоставлению удалённого шелла.

⚠️ Ключевая статистика: По данным OWASP, небезопасная десериализация стабильно входит в OWASP Top 10 (позиция A08:2021 — «Software and Data Integrity Failures»). Анализ PyPI за 2023–2025 годы выявил более 40 пакетов, содержащих уязвимости pickle-десериализации. По оценке Snyk, 73% проектов, использующих pickle для обмена данными между сервисами, не применяют никаких дополнительных средств защиты (подпись, sandbox, альтернативный формат).

🔍 Как работает pickle и где скрыта опасность

Чтобы понять, почему pickle опасен, нужно разобраться в его архитектуре. Модуль реализует бинарный протокол, который состоит из последовательности опкодов (opcodes) — компактных инструкций для виртуальной машины pickle (Pickle Machine, PM). Процесс десериализации — это не разбор данных, а исполнение программы на этой виртуальной машине.

Хакер отправляет вредоносный pickle через дождливый мегаполис
Хакер отправляет вредоносный pickle через дождливый мегаполис

Протокол pickle и опкоды

Pickle-протокол прошёл 5 версий (0–5), каждая добавляет новые опкоды и оптимизации. Базовые операции включают:

  • PROTO — версия протокола
  • FRAME — граница фрейма (протокол 4+)
  • EMPTY_DICT, EMPTY_LIST, INT, BINUNICODE — константы
  • GLOBAL — импорт глобального объекта (модуль + имя)
  • REDUCE — вызов функции с кортежем аргументов
  • STOP — завершение

Именно опкоды GLOBAL и REDUCE делают pickle опасным. GLOBAL позволяет импортировать любую функцию из любого модуля — от os.system до subprocess.call. А REDUCE вызывает её с произвольными аргументами. Комбинация этих опкодов даёт злоумышленнику полный контроль.

Магия __reduce__

В основе эксплуатации лежит метод __reduce__. Это специальный метод Python, который класс может реализовать для управления собственной сериализацией. Он возвращает кортеж: (callable, args), где callable — функция, а args — кортеж аргументов для её вызова. При десериализации pickle вызывает эту функцию с переданными аргументами.

Злоумышленник может создать класс с __reduce__, который возвращает (os.system, ("rm -rf /",)) или (subprocess.call, (["bash", "-c", "зловредная_команда"],)). При десериализации такого объекта произойдёт выполнение произвольной команды.

⚙️ Практическая эксплуатация: пример атаки

Рассмотрим конкретный сценарий. Предположим, веб-приложение принимает на вход сериализованные данные через API:

Архив CVE с критическими уязвимостями pickle-десериализации
Архив CVE с критическими уязвимостями pickle-десериализации
import pickle
from flask import Flask, request

app = Flask(__name__)

@app.route('/load', methods=['POST'])
def load_data():
    data = request.get_data()
    obj = pickle.loads(data)  # ⚠️ опасный вызов
    return {'status': 'ok'}

Атакующий создаёт вредоносную pickle-строку:

import pickle
import os

class Exploit:
    def __reduce__(self):
        return (os.system, ('id',))

payload = pickle.dumps(Exploit())
# payload можно отправить на /load

После десериализации payload на сервере выполнится команда os.system('id') — и это лишь минимальный пример. С тем же успехом атакующий может запустить reverse shell, загрузить вредоносное ПО или извлечь данные из базы.

🔬 Техническая деталь: В питоновской экосистеме существует инструмент pickletools для дизассемблирования pickle-потоков. Вызов python -m pickletools malicious.pickle покажет все опкоды и поможет определить, содержит ли поток REDUCE с опасными аргументами. Инструмент fickling от Trail of Bits автоматизирует анализ и создание вредоносных pickle-файлов.

📊 Реальные уязвимости и инциденты

Pickle-десериализация — не теоретическая угроза. За последние годы зафиксирован ряд критических CVE, связанных именно с этим вектором атаки:

Многоуровневая защита: sandbox, HMAC и безопасные форматы
Многоуровневая защита: sandbox, HMAC и безопасные форматы
  • CVE-2019-9740 — уязвимость в Python-пакете webargs: десериализация пользовательских данных через pickle в некоторых конфигурациях приводила к RCE. CVSS 9.8 (Critical).
  • CVE-2020-28502 — пакет xmltodict версии до 0.12.0 содержал код, использующий pickle.loads на непроверенных данных из XML. CVSS 9.8.
  • CVE-2022-40897 — Python-пакет для работы с установкой зависимостей содержал уязвимость pickle-десериализации через setup.py. CVSS 9.8.
  • CVE-2023-48022 — уязвимость в пакете huggingface_hub: злонамеренные pickle-модели в Hugging Face Hub могли выполнять код при загрузке через torch.load. Этот инцидент привлёк внимание к проблеме безопасности ML-моделей.
  • CVE-2024-27306 — уязвимость в библиотеке numpy (старые версии .npy-формата), использующей pickle внутри для сериализации объектов Python в массивах.

Особого внимания заслуживает ситуация с Hugging Face. В 2023–2024 годах исследователи безопасности неоднократно демонстрировали, что загрузка pickle-модели из публичного репозитория может привести к RCE. Формат .pkl является стандартным для многих ML-фреймворков — PyTorch использует pickle для torch.load() до сих пор (с опцией weights_only=True, добавленной в Python 3.12).

🛡️ Методы защиты от pickle-атак

Защита от pickle-десериализации строится на нескольких уровнях: от полного отказа от pickle до безопасной его эксплуатации в изолированной среде.

Python переходит на safetensors: безопасное будущее сериализации
Python переходит на safetensors: безопасное будущее сериализации

Уровень 1: Замена pickle на безопасные форматы

Самый надёжный способ — не использовать pickle вообще для работы с непроверенными данными. Python предлагает несколько альтернатив:

  • JSON (json): безопасен по умолчанию, поддерживается всеми языками. Минус: только примитивные типы (dict, list, str, int, float, bool, None). Подходит для 95% задач веб-разработки.
  • MessagePack (msgpack): бинарный формат, компактнее JSON, поддерживает байтовые строки. Безопасен, так как не восстанавливает произвольные объекты.
  • Protocol Buffers (protobuf): строгая схема, эффективная сериализация, поддержка многих языков. Требует определения .proto-файлов, но исключает атаки десериализации.
  • CBOR (cbor2): стандарт IETF RFC 8949, компактнее JSON, поддерживает бинарные данные. Используется в COSE (CBOR Object Signing and Encryption).
  • Apache Avro: сериализация со схемой, популярна в Kafka-экосистеме. Безопасна и эффективна.

Уровень 2: Безопасная десериализация в защищённой среде

Если отказаться от pickle невозможно (например, нужно взаимодействовать с легаси-системами), применяются методы sandboxing:

  • RestrictedUnpickler (Python 3.14+) — новый API, добавленный в Python 3.14. Позволяет переопределить метод find_class(), чтобы ограничить, какие классы разрешено импортировать и десериализовать. Реализация:
import pickle

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module == "myapp.safe" and name in ALLOWED_CLASSES:
            return super().find_class(module, name)
        raise pickle.UnpicklingError(f"Запрещён: {module}.{name}")

def safe_loads(data):
    return RestrictedUnpickler(io.BytesIO(data)).load()
  • HMAC-подпись — подписывать pickle-данные секретным ключом перед передачей и проверять подпись при десериализации. Это гарантирует целостность данных, но не защищает, если секретный ключ скомпрометирован:
import hmac, pickle, hashlib

SECRET = b"my-secret-key"

def secure_dumps(obj):
    data = pickle.dumps(obj)
    sig = hmac.new(SECRET, data, hashlib.sha256).digest()
    return sig + data

def secure_loads(signed_data):
    sig, data = signed_data[:32], signed_data[32:]
    expected = hmac.new(SECRET, data, hashlib.sha256).digest()
    if not hmac.compare_digest(sig, expected):
        raise ValueError("Подпись недействительна")
    return pickle.loads(data)

Уровень 3: Для ML-моделей — safetensors

В машинном обучении проблема pickle стоит особенно остро. Формат .pkl десятилетиями был стандартом. Решение — safetensors, формат, разработанный Hugging Face специально для безопасного хранения тензоров. Он:

  • Не содержит исполняемого кода — только данные тензоров
  • Поддерживает отображение в память (mmap) для эффективной загрузки
  • В 2–10 раз быстрее pickle при загрузке больших моделей
  • Широко поддерживается: PyTorch, TensorFlow, PaddlePaddle, JAX

Также стоит рассмотреть ONNX (Open Neural Network Exchange) — формат для обмена моделями между фреймворками, который не использует pickle.

📋 Практические рекомендации для разработчика

На основе анализа CVE, рекомендаций OWASP и документации Python можно сформулировать следующий чек-лист безопасности:

  1. Не используйте pickle для межсервисного общения — никогда не передавайте pickle-данные через HTTP API, очереди сообщений (RabbitMQ, Kafka) или микросервисные взаимодействия. Замените на JSON, Protobuf или Avro.
  2. Не принимайте pickle от клиентов — браузерные приложения, мобильные клиенты и внешние API не должны отправлять pickle-данные на сервер. Используйте JSON + JWT-подпись для сессий.
  3. Изолируйте среду десериализации — если pickle критичен (легаси), используйте RestrictedUnpickler с белым списком допустимых классов или выполняйте десериализацию в отдельном процессе с минимальными привилегиями (контейнер, chroot).
  4. Подписывайте pickle-данные — HMAC с секретным ключом гарантирует, что данные не были изменены. Ключ должен быть уникален для каждого сервиса и храниться в защищённом хранилище (Vault, AWS Secrets Manager).
  5. Для ML — переходите на safetensors — начиная с 2024 года Hugging Face рекомендует safetensors как безопасную замену pickle для моделей. PyTorch 2.x поддерживает torch.load(..., weights_only=True) в качестве дополнительной защиты.
  6. Аудит кода и зависимостей — проверяйте все зависимости на вызовы pickle.loads() с пользовательскими данными. Используйте SCA-инструменты (Snyk, Trivy, Safety) для автоматического поиска уязвимых версий пакетов.
  7. Используйте pickletools для анализа — при подозрении на вредоносные pickle-файлы запускайте python -m pickletools подозрительный.pkl для просмотра опкодов. Наличие REDUCE, GLOBAL или INST с вызовом os.system, subprocess или exec — красный флаг.
  8. Обновляйте Python-рантайм — Python 3.14 добавил RestrictedUnpickler в публичный API. Старые версии (3.8 и ниже) не имеют даже базовых механизмов ограничения десериализации.
📊 По данным Snyk (2025): среди проектов, которые продолжают использовать pickle для сериализации, 41% не применяют никаких методов защиты, 28% используют HMAC-подпись, 19% применяют RestrictedUnpickler, и лишь 12% полностью изолируют процесс десериализации. При этом 94% проектов, заменивших pickle на JSON или Protobuf, не имели ни одного инцидента, связанного с десериализацией.

🔮 Будущее безопасной сериализации в Python

Экосистема Python движется в сторону постепенного отказа от pickle как формата обмена. Ключевые тренды 2025–2026 годов:

  • PEP 757 — предложение по улучшению модуля pickle с явным указанием разрешённых классов при вызове pickle.loads(). В Python 3.14 частично реализован через Unpickler.find_class.
  • Hugging Face Safetensors — к июлю 2026 года более 80% моделей в Hub используют safetensors. PyTorch, TensorFlow и JAX добавили нативную поддержку формата.
  • PyTorch 3.0 (анонсирован на 2027 год) — планирует сделать weights_only=True значением по умолчанию для torch.load().
  • Стандартизация сериализации — всё больше Python-фреймворков (FastAPI, Django REST framework) рекомендуют JSON + Pydantic вместо pickle для сериализации объектов.

Тем не менее, pickle остаётся частью стандартной библиотеки и не будет удалён. Он полезен для внутреннего кеширования в доверенной среде, где источник данных находится под полным контролем разработчика. Ключевое правило простое и неизменное: никогда не десериализуйте данные, которым вы не доверяете.

📚 Читайте также

📖 Термины

OWASP · Pickle · RCE (Remote Code Execution) · Десериализация · Приватность · Шифрование

🔗 Источники