Kimai has an arbitrary file read in its invoice PDF renderer (admin)
Summary
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():phpif ($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:
phpif (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:phpif (isset($file['path'])) { $fileContent = @file_get_contents($file['path']); } ... $filestream = gzcompress($fileContent); $this->writer->write('<</Type /EmbeddedFile');
The 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.
Fix
The implemented fix has two aspects:
- The
PdfContextnow works with a strict allow-list, that excludesassociated_files - The
MPdfConverternow removes anypathfrom the$associatedFilesarray, which can still be used by plugins:
if (\count($associatedFiles) > 0) {
// remove "path" so mPDF will not use file_get_contents() on local files
// callers must pre-read and pass the bytes via "content"
$associatedFiles = array_map(static function ($entry): array {
if (!\is_array($entry)) {
return [];
}
if (\array_key_exists('path', $entry)) {
unset($entry['path']);
}
return $entry;
}, $associatedFiles);
$mpdf->SetAssociatedFiles($associatedFiles);
}
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Paczka Composer | kimai/kimai | >= 2.32.0, < 2.56 | 2.56 |
Odnośniki
- Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:N/A:N- Słabość (CWE)
CWE-22- Platforma
- Composer
- Paczka
kimai/kimai- Ujawniono
- 08.05.2026
- Aktualizacja
- 11.10.2026 10:22
Źródła i licencje
OSV / GitHub Advisory Database - GHSA-h5fh-7hwr-97mw - CC BY 4.0
Historia podatności: kimai/kimai
Wszystkie opisane podatności tej paczki (Paczka Composer), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.
- Wszystkich wpisów
- 33
- Pierwsza → ostatnia
- 02.2023 07.2026
- Bez poprawki
- 1
- Wykorzystywana w atakach
- 0
Rozkład istotności
- Krytyczna 2
- Wysoka 4
- Średnia 20
- Niska 7
Ujawnienia w latach
- 2023: 2
- 2024: 3
- 2025: 1
- 2026: 27
-
Kimai has an arbitrary file read in its invoice PDF renderer (admin) ten wpis
Dowiedz się, które podatności dotyczą Twojej aplikacji
Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.