GHSA-H5FH-7HWR-97MW
Vulnerability from github – Published: 2026-05-08 22:22 – Updated: 2026-05-08 22:22Summary
Users with the role System-Admin (ROLE_SYSTE_ADMIN) and the permission upload_invoice_template can upload PDF invoice templates, which can call pdfContext.setOption('associated_files', ...) inside the sandboxed Twig render.
This is forwarded to mPDF's SetAssociatedFiles(), whose writer calls file_get_contents($entry['path']) during PDF output and embeds the bytes as a FlateDecode stream in the PDF. Any file readable by the PHP worker is returned to the attacker inside the rendered invoice.
Root cause
-
src/Twig/SecurityPolicy/StrictPolicy.php:123-128explicitly whitelistsPdfContext::setOption():php if ($obj instanceof PdfContext) { if ($lcm !== 'setoption') { throw ...; } return; } -
src/Pdf/MPdfConverter.phpkeepsassociated_filesin the pass-through allowlist:php $allowed = ['mode','format','default_font_size','default_font', ... , 'associated_files','additional_xmp_rdf'];and then forwards it to mPDF:php if (array_key_exists('associated_files', $options) && is_array($options['associated_files'])) { $associatedFiles = $options['associated_files']; unset($options['associated_files']); } ... $mpdf->SetAssociatedFiles($associatedFiles); -
mPDF 8.3.1
MetadataWriter::writeAssociatedFiles()callsfile_get_contents, which respects PHP stream wrappers: ```php if (isset($file['path'])) { $fileContent = @file_get_contents($file['path']); } ... $filestream = gzcompress($fileContent); $this->writer->write('<
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.55"
},
"package": {
"ecosystem": "Packagist",
"name": "kimai/kimai"
},
"ranges": [
{
"events": [
{
"introduced": "2.32.0"
},
{
"fixed": "2.56"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44298"
],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-08T22:22:36Z",
"nvd_published_at": "2026-05-08T04:16:24Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nUsers with the role `System-Admin` (`ROLE_SYSTE_ADMIN`) and the permission `upload_invoice_template` can upload PDF invoice templates, which can call `pdfContext.setOption(\u0027associated_files\u0027, ...)` inside the sandboxed Twig render. \n\nThis is forwarded to mPDF\u0027s `SetAssociatedFiles()`, whose writer calls `file_get_contents($entry[\u0027path\u0027])` during PDF output and embeds the bytes as a FlateDecode stream in the PDF. Any file readable by the PHP worker is returned to the attacker inside the rendered invoice.\n\n## Root cause\n\n1. `src/Twig/SecurityPolicy/StrictPolicy.php:123-128` explicitly whitelists `PdfContext::setOption()`:\n ```php\n if ($obj instanceof PdfContext) {\n if ($lcm !== \u0027setoption\u0027) { throw ...; }\n return;\n }\n ```\n\n2. `src/Pdf/MPdfConverter.php` keeps `associated_files` in the pass-through allowlist:\n ```php\n $allowed = [\u0027mode\u0027,\u0027format\u0027,\u0027default_font_size\u0027,\u0027default_font\u0027, ... , \u0027associated_files\u0027,\u0027additional_xmp_rdf\u0027];\n ```\n and then forwards it to mPDF:\n ```php\n if (array_key_exists(\u0027associated_files\u0027, $options) \u0026\u0026 is_array($options[\u0027associated_files\u0027])) {\n $associatedFiles = $options[\u0027associated_files\u0027];\n unset($options[\u0027associated_files\u0027]);\n }\n ...\n $mpdf-\u003eSetAssociatedFiles($associatedFiles);\n ```\n\n3. mPDF 8.3.1 `MetadataWriter::writeAssociatedFiles()` calls `file_get_contents`, which respects PHP stream wrappers:\n ```php\n if (isset($file[\u0027path\u0027])) {\n $fileContent = @file_get_contents($file[\u0027path\u0027]);\n }\n ...\n $filestream = gzcompress($fileContent);\n $this-\u003ewriter-\u003ewrite(\u0027\u003c\u003c/Type /EmbeddedFile\u0027);\n ```\n\nThe sandbox and the option allowlist were both written defensively (short whitelists, not blacklists), but neither side considered that `associated_files` is a PDF/A file-embedding feature whose `path` key is a sink.\n\n## Fix\n\nThe implemented fix has two aspects:\n\n1. The `PdfContext` now works with a strict allow-list, that excludes `associated_files`\n2. The `MPdfConverter` now removes any `path` from the `$associatedFiles` array, which can still be used by plugins:\n```php\n if (\\count($associatedFiles) \u003e 0) {\n // remove \"path\" so mPDF will not use file_get_contents() on local files\n // callers must pre-read and pass the bytes via \"content\"\n $associatedFiles = array_map(static function ($entry): array {\n if (!\\is_array($entry)) {\n return [];\n }\n\n if (\\array_key_exists(\u0027path\u0027, $entry)) {\n unset($entry[\u0027path\u0027]);\n }\n\n return $entry;\n }, $associatedFiles);\n $mpdf-\u003eSetAssociatedFiles($associatedFiles);\n }\n\n```",
"id": "GHSA-h5fh-7hwr-97mw",
"modified": "2026-05-08T22:22:37Z",
"published": "2026-05-08T22:22:36Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/kimai/kimai/security/advisories/GHSA-h5fh-7hwr-97mw"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44298"
},
{
"type": "PACKAGE",
"url": "https://github.com/kimai/kimai"
},
{
"type": "WEB",
"url": "https://github.com/kimai/kimai/releases/tag/2.56.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Kimai has an arbitrary file read in its invoice PDF renderer (admin)"
}
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.