OSV 1.4.0 · github-reviewed · 修改于 2026-05-09 06:22
发布时间
2026-05-09 06:22
GitHub 审查时间
2026-05-09 06:22
NVD 发布时间
2026-05-08 12:16
源文件
advisories/github-reviewed/2026/05/GHSA-h5fh-7hwr-97mw/GHSA-h5fh-7hwr-97mw.json
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.
src/Twig/SecurityPolicy/StrictPolicy.php:123-128 explicitly whitelists PdfContext::setOption():
if ($obj instanceof PdfContext) {
if ($lcm !== 'setoption') { throw ...; }
return;
}
src/Pdf/MPdfConverter.php keeps associated_files in the pass-through allowlist:
$allowed = ['mode','format','default_font_size','default_font', ... , 'associated_files','additional_xmp_rdf'];
and then forwards it to mPDF:
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() calls file_get_contents, which respects PHP stream wrappers:
if (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.
The implemented fix has two aspects:
PdfContext now works with a strict allow-list, that excludes associated_filesMPdfConverter now removes any path from the $associatedFiles array, 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);
}