Эксплуатация небезопасной десериализации

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

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

Note

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

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

Идентифицировать небезопасную десериализацию относительно просто вне зависимости от того, проводите ли вы whitebox или blackbox тестирование.

Во время аудита изучайте все данные, поступающие на сайт, и пытайтесь определить всё, что похоже на сериализованные данные. Сериализованные данные можно распознать достаточно легко, если знать формат, используемый тем или иным языком. В этом разделе мы покажем примеры из сериализации в PHP и Java. Обнаружив сериализованные данные, проверьте, можете ли вы ими управлять.

Tip

Burp Scanner автоматически помечает любые HTTP‑сообщения, которые выглядят так, будто содержат сериализованные объекты.

Формат сериализации PHP

PHP использует в основном человекочитаемый строковый формат, где буквами представляется тип данных, а числами — длина каждого элемента. Например, рассмотрим объект User со следующими атрибутами:

В сериализованном виде этот объект может выглядеть так:

Это можно интерпретировать следующим образом:

  • O:4:"User" - объект с 4‑символьным именем класса "User"

  • 2 - у объекта 2 атрибута

  • s:4:"name" - ключ первого атрибута — 4‑символьная строка "name"

  • s:6:"carlos" - значение первого атрибута — 6‑символьная строка "carlos"

  • s:10:"isLoggedIn" - ключ второго атрибута — 10‑символьная строка "isLoggedIn"

  • b:1 - значение второго атрибута — булево true

Нативные методы сериализации в PHP — это serialize() и unserialize(). Если у вас есть доступ к исходному коду, начните с поиска unserialize() в кодовой базе и исследуйте дальше.

Формат сериализации Java

Некоторые языки, такие как Java, используют бинарные форматы сериализации. Их читать сложнее, но вы всё равно можете распознать сериализованные данные, если знаете несколько характерных признаков. Например, сериализованные Java‑объекты всегда начинаются с одинаковых байтов, кодируемых как ac ed в шестнадцатеричном виде и как rO0 в Base64.

Любой класс, реализующий интерфейс java.io.Serializable, может быть сериализован и десериализован. Если у вас есть доступ к исходникам, обратите внимание на любой код, использующий метод readObject(), который применяется для чтения и десериализации данных из InputStream.

Манипуляции сериализованными объектами

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

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

  • редактировать объект напрямую в виде байтового потока;

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

Второй подход часто проще при работе с бинарными форматами сериализации

Изменение атрибутов объекта

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

Простой пример: сайт использует сериализованный объект User для хранения данных о сессии пользователя в cookie. Если атакующий заметит этот сериализованный объект в HTTP‑запросе, он может декодировать его и увидеть следующий байтовый поток:

Атрибут isAdmin явно представляет интерес. Атакующий может просто поменять его булево значение на 1 (true), перекодировать объект и перезаписать свою текущую cookie этим изменённым значением. Само по себе это ничего не даёт. Но допустим, сайт использует эту cookie, чтобы проверять доступ текущего пользователя к административному функционалу:

Этот уязвимый код создаст объект User на основе данных из cookie, включая модифицированный атакующим атрибут isAdmin. Подлинность сериализованного объекта нигде не проверяется. Затем эти данные попадают в условие, что в данном случае позволит легко повысить привилегии.

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

Изменение типов данных

Мы увидели, как можно редактировать значения атрибутов в сериализованных объектах, но можно также подставлять неожиданные типы данных.

PHP‑логика особенно уязвима к такой манипуляции из‑за поведения «слабого» оператора сравнения (==) при сравнении разных типов. Например, при слабом сравнении между целым числом и строкой PHP попытается привести строку к целому, поэтому выражение 5 == "5" даст true.

Необычно, но это работает и для любой буквенно‑цифровой строки, начинающейся с числа. В таком случае PHP фактически преобразует всю строку в целое на основе начального числа, игнорируя остальную часть строки. Следовательно, 5 == "5 of something" на практике ведёт себя как 5 == 5.

Аналогично, в PHP 7.x и ниже сравнение 0 == "Example string" даёт true, поскольку PHP трактует всю строку как число 0.

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

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

Note

В PHP 8 и выше сравнение 0 == "Example string" даёт false, потому что строки больше не приводятся неявно к 0 при сравнениях. Поэтому такой эксплойт невозможен на этих версиях PHP.

Поведение при сравнении буквеннo‑цифровой строки, начинающейся с числа, в PHP 8 не изменилось. Соответственно, 5 == "5 of something" по‑прежнему трактуется как 5 == 5.

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

При работе напрямую с бинарными форматами рекомендуем использовать расширение Hackvertor из BApp Store. С Hackvertor вы можете изменять сериализованные данные как строку, а он автоматически обновит бинарные данные и скорректирует смещения. Это экономит массу ручной работы.

Использование функциональности приложения

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

Например, в рамках функции «Удалить пользователя» на сайте профильная картинка пользователя удаляется по пути файла из атрибута $user->image_location. Если этот $user был создан из сериализованного объекта, атакующий может эксплуатировать это, передав модифицированный объект с image_location, установленным в произвольный путь. Удаление собственной учётной записи тогда удалит и этот произвольный файл.

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

Магические методы

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

Разработчики могут добавлять магические методы в класс, чтобы задать код, который должен выполняться при соответствующем событии/сценарии. Точный момент и причина вызова магического метода зависят от конкретного метода. Один из самых распространённых примеров в PHP — __construct(), вызываемый при создании объекта класса, аналогично __init__ в Python. Как правило, такие конструкторы содержат код инициализации атрибутов экземпляра. Однако магические методы могут быть настроены разработчиками на выполнение любого кода.

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

Что особенно важно в данном контексте, некоторые языки имеют магические методы, которые вызываются автоматически во время процесса десериализации. Например, метод PHP unserialize() ищет и вызывает магический метод __wakeup() объекта.

В Java при десериализации аналогично обстоит дело с ObjectInputStream.readObject(), который используется для чтения данных из исходного потока байтов и фактически действует как конструктор для «повторной инициализации» сериализованного объекта. Однако классы, реализующие Serializable, могут объявлять свой собственный readObject() так:

Метод readObject(), объявленный именно так, действует как магический метод, вызываемый во время десериализации. Это позволяет классу более тонко контролировать десериализацию собственных полей.

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

Внедрение произвольных объектов

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

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

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

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

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

Цепочки гаджетов

«Гаджет» — это фрагмент кода, существующий в приложении, который помогает атакующему достичь конкретной цели. Отдельный гаджет может напрямую и не делать ничего вредоносного с пользовательским вводом. Однако цель атакующего может заключаться просто в вызове метода, который передаст его ввод в другой гаджет. Связывая несколько гаджетов таким образом, атакующий может в конечном итоге передать свой ввод в опасный «гаджет-приемник» (sink gadget), где он причинит максимальный ущерб.

Важно понимать, что, в отличие от некоторых других типов эксплойтов, цепочка гаджетов — это не полезная нагрузка из связанных методов, сконструированная атакующим. Весь код уже существует на сайте. Единственное, что контролирует атакующий, — это данные, передающиеся в цепочку гаджетов. Обычно это делается с помощью магического метода, вызываемого во время десериализации, иногда называемого «пусковым» гаджетом (kick‑off gadget).

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

Работа с готовыми цепочками гаджетов

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

Существуют инструменты, предоставляющие набор предварительно обнаруженных цепочек, успешно эксплуатированных на других сайтах. Даже без исходников вы можете использовать эти инструменты для идентификации и эксплуатации уязвимостей небезопасной десериализации с относительно небольшими усилиями. Такой подход возможен из‑за широкого использования библиотек, содержащих эксплуатируемые цепочки гаджетов. Например, если цепочку гаджетов из Java‑библиотеки Apache Commons Collections удалось эксплуатировать на одном сайте, любой другой сайт, использующий эту библиотеку, может быть уязвим к той же цепочке.

ysoserial

Один из таких инструментов для десериализации Java — «ysoserial». Он позволяет выбрать одну из предоставленных цепочек для библиотеки, которую, как вы полагаете, использует целевое приложение, затем передать команду, которую вы хотите выполнить. Инструмент создаёт соответствующий сериализованный объект на основе выбранной цепочки. Это всё ещё требует некоторой доли проб и ошибок, но куда менее трудозатратно, чем ручная сборка собственной цепочки.

Note

Java версии 16 и выше для запуска ysoserial нужно задать ряд аргументов командной строки. Например:

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

  • Цепочка URLDNS инициирует DNS‑запрос для заданного URL. Важно, что она не зависит от использования целевым приложением конкретной уязвимой библиотеки и работает в любой известной версии Java. Это делает её самой универсальной для целей детектирования. Если вы заметили сериализованный объект в трафике, можете попробовать сгенерировать этой цепочкой объект, который вызовет DNS‑взаимодействие с Burp Collaborator. Если взаимодействие есть, вы уверены, что на сервере произошла десериализация.

  • JRMPClient — другая универсальная цепочка для первичного детектирования. Она заставляет сервер попытаться установить TCP‑соединение с указанным IP‑адресом. Учтите, что нужно указывать IP, а не имя хоста. Эта цепочка полезна в средах, где весь исходящий трафик фильтруется, включая DNS. Можно пробовать генерировать пейлоады с двумя разными IP: локальным и внешним (за файрволом). Если приложение сразу отвечает на пейлоад с локальным адресом, но подвисает для внешнего, вызывая задержку ответа, это указывает, что цепочка сработала, поскольку сервер пытался подключиться к заблокированному адресу. В таком случае тонкая разница во времени ответов помогает выявить наличие десериализации даже при слепом тестировании.

PHP Generic Gadget Chains

Для большинства языков, часто страдающих от уязвимостей небезопасной десериализации, есть аналогичные PoC‑инструменты. Например, для PHP‑сайтов можно использовать «PHP Generic Gadget Chains» (PHPGGC).

Note

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

Работа с задокументированными цепочками гаджетов

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

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

Создание собственного эксплойта

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

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

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

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

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

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

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

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

PHAR‑десериализация

До сих пор мы рассматривали в основном эксплуатацию уязвимостей десериализации, когда сайт явно десериализует пользовательский ввод. Однако в PHP иногда возможно эксплуатировать десериализацию даже без явного использования метода unserialize(). PHP предоставляет несколько URL‑подобных обёрток для работы с разными протоколами при доступе к путям файлов. Одна из них — обёртка phar://, которая предоставляет потоковый интерфейс к PHP‑архивам (.phar).

Документация PHP сообщает, что манифесты PHAR содержат сериализованные метаданные. Критически важно, что при выполнении операций с файловой системой над потоком phar:// эти метаданные неявно десериализуются. Это означает, что поток phar:// потенциально может стать вектором для эксплуатации небезопасной десериализации при условии, что вы можете передать этот поток в метод работы с файловой системой. В случае очевидно опасных функций работы с файлами, таких как include() или fopen(), сайты, вероятно, внедряют контрмеры для снижения риска их злонамеренного использования. Однако такие методы, как file_exists(), не настолько явно опасны и могут быть защищены хуже.

Этот приём также требует, чтобы вы каким‑то образом загрузили PHAR на сервер. Один из подходов — использовать функциональность загрузки изображений. Если вы можете создать полиглот‑файл, где PHAR маскируется под обычный JPG, вы иногда можете обойти проверки валидности сайта. Если затем вы сможете заставить сайт загрузить этот JPG‑полиглот через поток phar://, любые вредоносные данные, внедрённые через метаданные PHAR, будут десериализованы. Поскольку при чтении потока PHP не проверяет расширение файла, не важно, что у файла расширение картинки.

Пока класс объекта поддерживается сайтом, магические методы __wakeup() и __destruct() могут быть вызваны таким образом, позволяя потенциально запустить цепочку гаджетов. Эта изобретательная техника вошла в наш список Top 10 web hacking techniques of 2018.

Эксплуатация десериализации через ошибки повреждения памяти

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

Методы десериализации, такие как unserialize() в PHP, редко защищены против таких атак и открывают огромную поверхность атаки. Это не всегда считается уязвимостью само по себе, поскольку эти методы изначально не предназначены для обработки контролируемого пользователем ввода.

Last updated