Сериализация против десериализации

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

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

Что такое небезопасная десериализация?

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

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

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

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

Использование небезопасной десериализации в PHP

Основы десериализации PHP

Строки 2–15: Объявление класса PHP с именем Car, который имеет 3 атрибута: модель, производитель и цвет. У каждого из них есть разные спецификаторы доступа для демонстрационных целей. Параметризованный конструктор используется для инициализации атрибутов.

Строка 16: Создание объекта класса Car.

Строки 18,19: сериализация объекта, созданного в строке №16. Сериализация создает некоторые непечатаемые символы, такие как \ x00, поэтому мы заменяем его на \\ x00, чтобы мы могли правильно просматривать вывод.

O:3:"Car":3:{...}
Objects start with upper case O, followed by the the length of the class name(which is 3 because length of the word car is 3), followed by name of the class i.e. Car, followed by number of attribues in the class which is 3 (model, manufaturer and colour).
s:10:"\x00Car\x00model";s:6:"SELTOS";
This is an attribute of the class car with it's corresponding value. 's' means a string attribute followed by the length of the name of attribute. Notice how the access specifier(private) is appended before the attribute name while serialization. \x00ClassName\x00 is the format followed in case of private attributes. This is followed by the type (s: string), length (6 characters in SELTOS) and value (SELTOS) of the attribute (model).
s:15:"\x00*\x00manufacturer";s:3:"KIA";
This is also similar to the above example however, as this attribute is of protected type, \x00*\x00 is appended before the attribute name.
s:6:"colour";s:5:"WHITE";
For public attributes nothing is appended to the attribute name. The remaining part is similar to the other attributes.
Some other types of data can also be present like i for integer and b for boolean. The lenghth of the value is not required in this case.
s:6:wheels;i:4; is similar to public int wheels = 4
s:10:twowheeler;b:0; is similar to public boolean twowheeler = false

Управление сериализованными объектами

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

После входа в приложение мы видим, что файл cookie сеанса установлен и его кодировка base64. После декодирования значения мы видим, что это сериализованный объект PHP.

В этом файле cookie есть интересный атрибут: admin, для которого установлено значение 0. Мы изменяем это значение на 1, кодируем его обратно в base64 и сохраняем новое значение cookie.

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

В бэкэнд-коде мы видим, что приложение слепо доверяет сериализованным данным (строка 7) и обеспечивает управление доступом в соответствии со значениями атрибутов объекта (строка 21). Как можно изменить этот атрибут объекта и повысить наши привилегии.

Управление типами данных PHP и сравнения

Этот код в основном является доказательством концепции того, как PHP обрабатывает типы данных и нестрогие сравнения и как ими можно управлять. После открытия этой страницы и нажатия кнопки установки / сброса устанавливается cookie. Этот файл cookie представляет собой сериализованный объект класса Secret , который содержит username и token. По умолчанию он покажет, что у пользователя низкие привилегии, однако, если имя пользователя - admin, а токен установлен на суперсекретный токен администратора, можно получить доступ к разделу администратора. В следующем разделе мы увидим, как можно обойти эти требования, манипулируя тем, как PHP сравнивает данные.

Вольное сравнение PHP довольно странно из-за следующих характеристик:

  • При сравнении целого числа и строки PHP преобразует строку в целое число. Это означает 5 == "5"
  • При сравнении буквенно-цифровой строки с числом он сначала проверяет, начинается ли строка с числа. Если он начинается с числа, остальная часть строки игнорируется. Это означает, что2 == "2 some random string" эквивалентно 2 == "2".
  • Если эта строка не содержит целого числа, то она преобразуется в 0, вероятно, потому, что в ней 0 чисел. Так что 0 == "Somestring" это правда.

В этом эксплойте мы сделаем то же самое. Мы изменим имя на admin и изменим токен на целое число 0.

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

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

Магические методы и произвольные объекты

Магические методы - это специальные функции, которые вызываются автоматически. Эти функции начинаются с двойного подчеркивания, например , __construct(). Метод __construct() вызывается, как только создается новый объект. Разработчик может реализовать свой собственный __construct() метод для реализации параметризованного конструктора. Есть несколько других магических методов, но мы сосредоточимся на __destruct() и __wakeup().

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

В этом коде есть класс Serialkiller, который принимает имя пользователя в качестве входных данных в конструкторе.

  • После этого он инициализирует пути к файлам для журнала и кеша. Файлы будут созданы в каталогах /logs и /cache.
  • Есть вспомогательный метод createFile(), который создает файлы, и logEntry(), который записывает данные в файлы.
  • __destroy() волшебный метод удаляет файл кеша пользователя по завершении выполнения скрипта.
  • Магический метод __wakeup() повторно инициализирует файл журнала и файлы кэша, если он не существует, а затем создает запись в журнале.

После объявления класса мы создаем экземпляр класса Serialkiller и записываем тестовый журнал. В строках 43–50 мы печатаем сериализованную форму созданного ранее экземпляра. Мы заменяем \x00 его URL-кодированной формой %00. Мы изменим этот вывод для создания наших эксплойтов.

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

После выполнения скрипта мы получаем сериализованный объект, который будем использовать для разработки эксплойта. Для первого эксплойта мы удалим файл test.txt из корневого веб-каталога, манипулируя функцией __destruct.

Мы изменим значение атрибута cache_file на test.txt в сериализованном объекте и десериализуем его, передав его в качестве входных данных для параметра данных. После десериализации атрибут cache_file указывает на test.txt. Когда вызывается метод __destruct (), он удаляет test.txt (строка 27).

O:12:"Serialkiller":4:{s:24:"%00Serialkiller%00cache_file";s:8:"test.txt";s:22:"%00Serialkiller%00log_file";s:13:"logs/john.log";s:21:"%00Serialkiller%00content";s:12:"Starting log";s:18:"%00Serialkiller%00user";s:4:"john";}

На следующем снимке экрана мы можем убедиться, что файл test.txt был удален.

Для следующего эксплойта мы собираемся выполнить код на сервере, создав вредоносный файл PHP на сервере и выполнив его. Для этого мы воспользуемся функцией __wakeup().

Мы знаем, что __wakeup() вызывается, как только объект десериализуется. Внутри функции он создает файл журнала, а также добавляет запись в журнал. Мы будем манипулировать значениями переменных таким образом, чтобы вместо создания файла журнала создавался файл PHP, и вместо записи записи в файл PHP записывалась какая-то вредоносная команда PHP. Мы следуем приведенным ниже инструкциям, чтобы воспользоваться этим

  • Мы изменяем атрибут log_file в полезной нагрузке на целевой путь к файлу PHP RCE, который мы хотим сгенерировать (logs/rce.php в примере ниже). Как только createFile () вызывается с ним, он проверит, существует ли файл logs/rce.php (который является значением переменной log_file), и создаст его, если он отсутствует (Строки 21,35).
  • Точно так же мы изменим атрибут content в полезной нагрузке на некоторый PHP-код, например <?php system('dir C:\\'); ?>. Метод logEntry(), вызываемый в строке 22, принимает значение переменной content и записывает его в log_file. Таким образом, вредоносный код PHP будет записан в /logs/rce.php файл.

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

O:12:"Serialkiller":4:{s:24:"%00Serialkiller%00cache_file";s:16:"cache/john.cache";s:22:"%00Serialkiller%00log_file";s:12:"logs/rce.php";s:21:"%00Serialkiller%00content";s:28:"<?php system('dir C:\\'); ?>";s:18:"%00Serialkiller%00user";s:4:"john";}

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

http://localhost/injection.php?data=O:12:"Serialkiller":4:{s:24:"%00Serialkiller%00cache_file";s:16:"cache/john.cache";s:22:"%00Serialkiller%00log_file";s:12:"logs/rce.php";s:21:"%00Serialkiller%00content";s:28:"<?php system('dir C:\\'); ?>";s:18:"%00Serialkiller%00user";s:4:"john";}

После выполнения вышеуказанной полезной нагрузки мы переходим к новому файлу PHP, созданному в http://localhost/logs/rce.php. Мы видим, что можно выполнять код.

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

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

Дальнейшее чтение