Pickle-десериализация в Python: как одна строка кода может привести к выполнению произвольных команд

53646
Pickle-десериализация в Python: как одна строка кода может привести к выполнению произвольных команд
image

Практически каждый Python-разработчик хотя бы раз использовал pickle. Этот модуль встроен в стандартную библиотеку, работает буквально в две строки и умеет сохранять практически любые объекты Python без дополнительной настройки.

Именно поэтому pickle часто используют для хранения состояния приложения, передачи объектов между процессами, кэширования, работы с ML-моделями и внутренних сервисов.

Но у такого удобства есть обратная сторона. В отличие от JSON, pickle восстанавливает не просто данные, а полноценные объекты Python. Если приложение десериализует данные, которыми управляет атакующий, это может закончиться выполнением произвольного кода на сервере. Не случайно небезопасная десериализация уже много лет входит в число наиболее опасных ошибок безопасности веб-приложений.

В этой статье разберем, почему так происходит, как устроен механизм Pickle, по каким признакам пентестер может обнаружить подобную проблему и что нужно делать разработчикам, чтобы не превратить сериализацию в точку входа для злоумышленника.

В конце статьи можно перейти к практическому заданию из бесплатного курса «Профессия Белый Хакер» и проверить, как небезопасная десериализация работает в учебном приложении.

Что такое сериализация и зачем она нужна

Программа не может сохранить объект Python в файл или передать его по сети "как есть". Для этого объект сначала преобразуют в последовательность байтов. Этот процесс называется сериализацией.

Обратная операция — восстановление объекта из сохраненных данных — называется десериализацией. Именно она представляет основной интерес с точки зрения безопасности.

Сериализация используется практически в любом приложении:

  • сохранение состояния программы;
  • кэширование;
  • обмен данными между сервисами;
  • передача объектов между процессами;
  • хранение результатов вычислений.

Для большинства подобных задач применяют универсальные форматы вроде JSON, XML или YAML. Однако они умеют хранить только простые структуры данных. Если необходимо сохранить полноценный объект Python со всеми его свойствами, разработчики часто выбирают Pickle.

Почему Pickle отличается от JSON

На первый взгляд JSON и Pickle решают одну задачу — позволяют сохранить данные. Однако работают они по-разному.

После десериализации JSON приложение получает только стандартные структуры данных: словари, списки, строки, числа и логические значения. При этом никакие функции не вызываются.

Pickle предназначен для восстановления объектов Python. Чтобы воссоздать объект, интерпретатор читает сериализованный поток и выполняет действия, необходимые для его реконструкции: создает объекты, восстанавливает их состояние и при необходимости вызывает связанные механизмы восстановления.

Именно поэтому десериализация Pickle принципиально отличается от обычного разбора JSON. Она не ограничивается чтением данных, а участвует в восстановлении объектов. Если приложение передает в pickle.loads() данные, которыми управляет атакующий, механизм восстановления может быть использован для выполнения произвольного кода.

По этой причине разработчики Python рекомендуют никогда не десериализовать с помощью Pickle данные из недоверенных источников.

Метод __reduce__: механизм, который делает возможным RCE

Главную роль в большинстве атак играет специальный метод __reduce__.

Большинство разработчиков никогда его не используют напрямую. Тем не менее именно он определяет, каким образом объект должен быть восстановлен после десериализации.

Если очень упростить процесс, то __reduce__ отвечает на вопрос:

"Как заново создать именно этот объект?"

Вместо готового объекта метод возвращает описание действий, которые необходимо выполнить.

Например:

  • какую функцию вызвать;
  • какие аргументы ей передать;
  • какое состояние затем восстановить.

В обычных приложениях это абсолютно легальный механизм.

Однако проблема появляется тогда, когда описание объекта формирует атакующий.

В вашем учебном материале приведен классический пример: метод __reduce__ возвращает вызов системной функции с заданными аргументами. Во время десериализации эта функция автоматически выполняется.

Именно поэтому говорят, что Pickle способен выполнять код при десериализации.

На самом деле код не "спрятан" внутри файла.

Он вызывается самим механизмом восстановления объекта.

Почему это считается удаленным выполнением кода

Представим, что приложение получает сериализованный объект через HTTP-запрос и без дополнительной проверки передает его в pickle.loads():

obj = pickle.loads(request.data)

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

Именно поэтому небезопасная десериализация входит в число наиболее опасных ошибок безопасности веб-приложений и может приводить к удаленному выполнению кода.

Почему Base64 ничего не меняет

Во многих приложениях сериализованные данные дополнительно кодируют в Base64.

Например:

gASVOAAAAAAAAACM...

Из-за этого создается впечатление, что данные стали безопаснее. На деле всё не так.

Base64 — всего лишь способ представить бинарные данные в текстовом виде. Он не шифрует содержимое. После декодирования приложение получает тот же самый поток байтов Pickle и передает его в pickle.loads(). Для атакующего это практически ничего не меняет.

Где чаще всего встречается Pickle

Многие считают, что Pickle используют только начинающие разработчики.

На практике это не так.

Он широко применяется внутри доверенной инфраструктуры, где риск считается приемлемым.

Например:

  • сохранение результатов обучения моделей машинного обучения;
  • обмен объектами между процессами Python;
  • внутренние очереди задач;
  • кэширование сложных объектов;
  • сохранение состояния приложений;
  • различные служебные файлы.

Сам по себе Pickle не является уязвимостью.

Проблема начинается только тогда, когда поток сериализованных данных оказывается под контролем пользователя.

Именно этот момент обычно становится отправной точкой во время аудита безопасности.

Как пентестер обнаруживает небезопасную десериализацию

На практике никто не начинает поиск уязвимости с чтения исходного кода класса Exploit. Обычно всё происходит наоборот: сначала исследователь замечает подозрительные признаки, а уже потом пытается понять, используется ли в приложении Pickle.

Первый источник информации — сам код приложения.

Во время аудита стоит обращать внимание на вызовы:

pickle.loads(...)
pickle.load(...)

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

Но далеко не всегда исходный код доступен.

Во время внешнего тестирования приходится искать косвенные признаки.

Например, приложение может получать длинную строку в параметре запроса, cookie или теле POST-запроса. Иногда она выглядит как случайный набор символов, но после декодирования Base64 оказывается сериализованным объектом Pickle.

Поэтому опытный пентестер всегда задает себе несколько вопросов:

  • откуда приложение получает эти данные;
  • проходят ли они проверку перед десериализацией;
  • действительно ли они были сформированы самим приложением;
  • можно ли изменить их содержимое.

Именно цепочка «получение пользовательских данных → pickle.loads()» представляет наибольший интерес.

Какие признаки выдают использование Pickle

В реальном приложении редко встречается комментарий вроде:

«Здесь находится опасная десериализация».

Чаще приходится собирать картину по косвенным признакам.

Вот несколько вещей, которые обычно привлекают внимание во время аудита:

  • использование модулей pickle, dill или joblib;
  • сериализованные данные, передаваемые в Base64;
  • сохранение объектов Python в файлы с расширениями .pkl, .pickle или .joblib;
  • внутренние API, принимающие сериализованные объекты;
  • кэширование сложных структур данных.

Сам по себе ни один из этих признаков не означает наличие уязвимости.

Например, сохранение модели машинного обучения в файл Pickle — совершенно нормальная практика.

Опасность появляется только тогда, когда содержимое сериализованного объекта начинает контролировать пользователь.

Именно поэтому задача пентестера состоит не в поиске Pickle как такового, а в поиске места, где недоверенные данные попадают в десериализацию.

Почему фильтрация редко помогает

Иногда разработчики понимают риск и пытаются защититься с помощью различных проверок.

Например:

  • запрещают определенные строки;
  • удаляют отдельные символы;
  • проверяют размер объекта;
  • фильтруют содержимое после декодирования Base64.

К сожалению, подобные меры редко дают надежную защиту.

Причина проста.

Проблема заключается не в конкретной строке внутри сериализованных данных.

Опасен сам механизм восстановления объекта.

Если приложение доверяет данным настолько, что передает их в pickle.loads(), оно автоматически соглашается выполнить все инструкции, необходимые для реконструкции объекта.

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

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

Где подобные ошибки встречаются чаще всего

Большинство подобных уязвимостей появляется вовсе не в публичных API.

Наоборот.

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

Например:

  • сервис обменивается объектами с другим внутренним сервисом;
  • приложение сохраняет состояние в Redis;
  • очередь задач передает объекты между процессами;
  • модель машинного обучения загружается из заранее подготовленного файла.

Со временем инфраструктура меняется.

Появляются новые интеграции.

Добавляется импорт пользовательских файлов.

Появляется возможность загрузить резервную копию или конфигурацию.

Именно в этот момент граница доверия может незаметно измениться.

Код при этом остается прежним.

В результате функция, которая раньше работала только с доверенными данными, начинает принимать объекты от пользователя.

Так и возникает классическая уязвимость небезопасной десериализации.

Как защищаться

Главное правило очень простое.

Не используйте Pickle для данных, происхождение которых невозможно полностью контролировать.

Если необходимо обмениваться информацией между клиентом и сервером, лучше выбрать формат, который описывает только данные, а не процесс восстановления объектов.

Чаще всего подходят:

  • JSON;
  • MessagePack;
  • Protocol Buffers;
  • другие форматы, не поддерживающие выполнение кода при десериализации.

Если отказаться от Pickle невозможно, стоит соблюдать несколько правил.

Во-первых, сериализованные данные должны поступать только из доверенного источника.

Во-вторых, полезно проверять их целостность с помощью цифровой подписи или HMAC. Это не делает Pickle безопасным сам по себе, но позволяет обнаружить попытку подмены данных.

В-третьих, необходимо четко понимать границы доверия. Даже если сегодня сериализованные объекты создаются исключительно вашим приложением, через год архитектура может измениться. Именно такие изменения нередко становятся причиной появления уязвимостей.

Что стоит запомнить перед практикой

Теперь вернемся к тому, с чего начали.

Pickle сам по себе не является уязвимостью.

Это удобный механизм сериализации объектов Python, который отлично подходит для работы внутри доверенной среды.

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

Именно поэтому во время анализа кода стоит обращать внимание не только на наличие модуля pickle, но и на происхождение данных, передаваемых в pickle.loads().

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

  • Чем Pickle принципиально отличается от JSON?
  • Почему десериализация Pickle может привести к выполнению кода?
  • Какие функции Python отвечают за сериализацию и десериализацию?
  • Какие признаки позволяют заподозрить использование Pickle во время аудита?
  • Почему кодирование в Base64 не устраняет проблему?
  • В какой момент безопасный механизм сериализации превращается в уязвимость?

Если на все эти вопросы есть ответы, значит, у вас уже есть всё необходимое, чтобы приступить к поиску уязвимости в учебном приложении. Скорее всего, искать придется не «магический эксплойт», а место, где нарушена граница доверия между пользователем и механизмом десериализации. Именно это и является ключевой идеей большинства задач на небезопасную десериализацию.

Проверьте знания на практике

Теория помогает понять, как устроена небезопасная десериализация, но по-настоящему разобраться в теме можно только на практике.

Для этого подойдет задача «Элитный сомелье»*, доступная студентам бесплатного курса «Профессия Белый Хакер».

По сюжету ваш знакомый сомелье Володя устроился работать в модный ресторан и заодно запустил собственный сайт о вине. Теперь он рассуждает о «нотках дуба» и «послевкусии чернослива», но совсем забыл о безопасности своего приложения.

Известно, что сайт использует сериализацию с помощью Pickle. Ваша задача — исследовать приложение, добиться выполнения произвольных команд и получить флаг, расположенный в корне файловой системы.

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

Разбирайте решения на открытых вебинарах

ONE TASK — это не только самостоятельная практика, но и регулярные открытые разборы. Примерно раз в месяц студенты бесплатного курса «Профессия Белый Хакер» получают возможность разобрать накопившиеся задачи вместе с экспертом.

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

Формат полезен тем, что показывает не просто готовое решение, а мышление действующего специалиста.

Следите за анонсами открытых вебинаров, регистрируйтесь на бесплатный курс «Профессия Белый Хакер» и используйте ONE TASK как регулярную практику для роста в пентесте.

*Чтобы открыть практическое задание «Элитный сомелье», нужно зарегистрироваться на бесплатном курсе «Профессия Белый Хакер». После регистрации задание станет доступно в личном кабинете.

10 000 человек уже думают как хакеры
Ты — пока нет. Реальные атаки, реальные стенды, реальная разница между «знаю» и «умею».
Присоединиться →
WHITE HAT // verified
РЕКЛАМА