Moderate severityNVD Advisory· Published Oct 31, 2023· Updated Sep 5, 2024
Pimcore Admin Classic Bundle Cross-site Scripting (XSS) in PDF previews
CVE-2023-46722
Description
The Pimcore Admin Classic Bundle provides a backend UI for Pimcore. Prior to version 1.2.0, a cross-site scripting vulnerability has the potential to steal a user's cookie and gain unauthorized access to that user's account through the stolen cookie or redirect users to other malicious sites. Users should upgrade to version 1.2.0 to receive a patch or, as a workaround, apply the patch manually.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pimcore/admin-ui-classic-bundlePackagist | < 1.2.0 | 1.2.0 |
Affected products
1- Range: < 1.2.0
Patches
219fda2e86557[Improvement]: Add sanitizing pdf (#301)
5 files changed · +156 −7
src/Controller/Admin/Asset/AssetController.php+48 −6 modified@@ -33,6 +33,7 @@ use Pimcore\Loader\ImplementationLoader\Exception\UnsupportedException; use Pimcore\Logger; use Pimcore\Messenger\AssetPreviewImageMessage; +use Pimcore\Messenger\AssetUpdateTasksMessage; use Pimcore\Model; use Pimcore\Model\Asset; use Pimcore\Model\DataObject\ClassDefinition\Data\ManyToManyRelation; @@ -984,6 +985,13 @@ public function showVersionAction(Request $request): Response throw $this->createAccessDeniedHttpException('Permission denied, version id [' . $id . ']'); } + if ($asset->getMimeType() === 'application/pdf') { + $scanResponse = $this->getResponseByScanStatus($asset, false); + if ($scanResponse) { + return $scanResponse; + } + } + $loader = \Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data'); return $this->render( @@ -1487,12 +1495,11 @@ protected function addThumbnailCacheHeaders(Response $response): void /** * @Route("/get-preview-document", name="pimcore_admin_asset_getpreviewdocument", methods={"GET"}) - * - * @param Request $request - * - * @return StreamedResponse */ - public function getPreviewDocumentAction(Request $request): StreamedResponse + public function getPreviewDocumentAction( + Request $request, + TranslatorInterface $translator + ): StreamedResponse|Response { $asset = Asset\Document::getById((int) $request->get('id')); @@ -1501,6 +1508,13 @@ public function getPreviewDocumentAction(Request $request): StreamedResponse } if ($asset->isAllowed('view')) { + if ($asset->getMimeType() === 'application/pdf') { + $scanResponse = $this->getResponseByScanStatus($asset); + if ($scanResponse) { + return $scanResponse; + } + } + $stream = $this->getDocumentPreviewPdf($asset); if ($stream) { return new StreamedResponse(function () use ($stream) { @@ -1516,6 +1530,29 @@ public function getPreviewDocumentAction(Request $request): StreamedResponse } } + private function getResponseByScanStatus(Asset\Document $asset, bool $processBackground = true) :?Response + { + if (!Config::getSystemConfiguration('assets')['document']['scan_pdf']) { + return null; + } + + $scanStatus = $asset->getScanStatus(); + if ($scanStatus === null) { + $scanStatus = Asset\Enum\PdfScanStatus::IN_PROGRESS; + if ($processBackground) { + \Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch( + new AssetUpdateTasksMessage($asset->getId()) + ); + } + } + + return match($scanStatus) { + Asset\Enum\PdfScanStatus::IN_PROGRESS => $this->render('@PimcoreAdmin/admin/asset/get_preview_pdf_in_progress.html.twig'), + Asset\Enum\PdfScanStatus::UNSAFE => $this->render('@PimcoreAdmin/admin/asset/get_preview_pdf_unsafe.html.twig'), + default => null, + }; + } + /** * @param Asset\Document $asset * @@ -1529,7 +1566,12 @@ protected function getDocumentPreviewPdf(Asset\Document $asset) $stream = $asset->getStream(); } - if (!$stream && $asset->getPageCount() && \Pimcore\Document::isAvailable() && \Pimcore\Document::isFileTypeSupported($asset->getFilename())) { + if ( + !$stream && + $asset->getPageCount() && + \Pimcore\Document::isAvailable() && + \Pimcore\Document::isFileTypeSupported($asset->getFilename()) + ) { try { $document = \Pimcore\Document::getInstance(); $stream = $document->getPdf($asset);
templates/admin/asset/get_preview_pdf_in_progress.html.twig+51 −0 added@@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + + <style> + + /* hide from ie on mac \*/ + html { + height: 100%; + overflow: hidden; + } + /* end hide */ + + body { + height: 100%; + margin: 0; + padding: 0; + background: #EEE; + } + + #container { + text-align: center; + position: absolute; + top:50%; + margin-top: -200px; + width: 100%; + } + + #message { + margin-left: 8px; + } + + </style> + + <script {{ pimcore_csp.getNonceHtmlAttribute()|raw }}> + window.setTimeout(() => { + window.location.reload(); + }, 5000) + </script> +</head> + +<body> + +<div id="container"> + <span id="message">{{ 'pdf_scan_in_progress'|trans([], 'admin') }}</span> +</div> + + +</body> +</html>
templates/admin/asset/get_preview_pdf_unsafe.html.twig+54 −0 added@@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + + <style> + + /* hide from ie on mac \*/ + html { + height: 100%; + overflow: hidden; + } + /* end hide */ + + body { + height: 100%; + margin: 0; + padding: 0; + background: #EEE; + } + + #container { + text-align: center; + position: absolute; + top:50%; + margin-top: -200px; + width: 100%; + } + + #warning { + position: relative; + width: 40px; + height: 40px; + top: 13px; + } + + #message { + margin-left: 8px; + } + + </style> + +</head> + +<body> + +<div id="container"> + <img alt="warning" id="warning" src="/bundles/pimcoreadmin/img/flat-color-icons/overlay-error.svg" /> + <span id="message">{{ 'pdf_js_unsafe'|trans([], 'admin') }}</span> +</div> + + +</body> +</html>
templates/admin/asset/show_version_document.html.twig+1 −1 modified@@ -1,7 +1,7 @@ {% if asset.getMimeType() == 'application/pdf' %} {% set tempFile = asset.getTemporaryFile() %} {% set dataUri = pimcore_asset_version_preview(tempFile) %} - + <div style="display: flex; width: 100%; height: 100%; flex-direction: column; overflow: hidden;"> <iframe src="{{ dataUri }}" frameborder="0" style="flex-grow: 1; border: none; margin: 0; padding: 0;"></iframe> </div>
translations/admin.en.yaml+2 −0 modified@@ -988,3 +988,5 @@ allow_asset_inline_download: Allow inline download system_appearance_settings: 'Appearance & Branding' male: Male female: Female +pdf_js_unsafe: This PDF file contains JavaScript. If you want to view it, please download and open it in your local PDF viewer. +pdf_scan_in_progress: 'Preview not available: PDF is being scanned. This may take a while.'
757375677dc8Implement Asset Sanitizer Queue & Preview Check (#16053)
7 files changed · +93 −0
bundles/CoreBundle/src/DependencyInjection/Configuration.php+4 −0 modified@@ -553,6 +553,10 @@ private function addAssetNode(ArrayNodeDefinition $rootNode): void ->defaultTrue() ->info('Process text for Asset documents (e.g. used by backend search).') ->end() + ->booleanNode('scan_pdf') + ->defaultTrue() + ->info('Scan PDF documents for unsafe JavaScript.') + ->end() ->end() ->end() ->arrayNode('versions')
doc/04_Assets/README.md+1 −0 modified@@ -28,6 +28,7 @@ pimcore: enabled: false #disable generating thumbnail for asset documents process_page_count: false #disable processing page count process_text: false #disable processing text extraction + scan_pdf: false #disable scanning PDF documents for unsafe JavaScript. ``` The sub chapters of this chapter provide insight into details for
doc/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md+2 −0 modified@@ -20,6 +20,8 @@ pimcore: enabled: false #disable generating thumbnail for asset documents process_page_count: false #disable processing page count process_text: false #disable processing text extraction + scan_pdf: false #disable scanning PDF documents for unsafe JavaScript. + ``` - [Elements] Properties are now only updated in the database with dirty state (when calling `setProperties` or `setProperty`). - `Pimcore\Helper\CsvFormulaFormatter` has been deprecated. Use `League\Csv\EscapeFormula` instead.
lib/Messenger/Handler/AssetUpdateTasksHandler.php+4 −0 modified@@ -62,6 +62,10 @@ private function saveAsset(Asset $asset): void private function processDocument(Asset\Document $asset): void { + if ($asset->getMimeType() === 'application/pdf' && $asset->checkIfPdfContainsJS()) { + $asset->save(['versionNote' => 'PDF scan result']); + } + $pageCount = $asset->getCustomSetting('document_page_count'); if (!$pageCount || $pageCount === 'failed') { if ($asset->processPageCount()) {
models/Asset/Document.php+54 −0 modified@@ -26,6 +26,8 @@ */ class Document extends Model\Asset { + public const CUSTOM_SETTING_PDF_SCAN_STATUS = 'document_pdf_scan_status'; + protected string $type = 'document'; protected function update(array $params = []): void @@ -163,6 +165,53 @@ public function getText(int $page = null): ?string return null; } + public function checkIfPdfContainsJS(): bool + { + if (!$this->isPdfScanningEnabled()) { + return false; + } + + $this->setCustomSetting( + self::CUSTOM_SETTING_PDF_SCAN_STATUS, + Model\Asset\Enum\PdfScanStatus::IN_PROGRESS->value + ); + + $chunkSize = 1024; + $filePointer = $this->getStream(); + + $tagLength = strlen('/JS'); + + while ($chunk = fread($filePointer, $chunkSize)) { + if (strlen($chunk) <= $tagLength) { + break; + } + + if (str_contains($chunk, '/JS') || str_contains($chunk, '/JavaScript')) { + $this->setCustomSetting( + self::CUSTOM_SETTING_PDF_SCAN_STATUS, + Model\Asset\Enum\PdfScanStatus::UNSAFE->value + ); + return true; + } + } + + $this->setCustomSetting( + self::CUSTOM_SETTING_PDF_SCAN_STATUS, + Model\Asset\Enum\PdfScanStatus::SAFE->value + ); + + return true; + } + + public function getScanStatus(): ?Model\Asset\Enum\PdfScanStatus + { + if ($scanStatus = $this->getCustomSetting(self::CUSTOM_SETTING_PDF_SCAN_STATUS)) { + return Model\Asset\Enum\PdfScanStatus::tryFrom($scanStatus); + } + + return null; + } + private function isThumbnailsEnabled(): bool { return Config::getSystemConfiguration('assets')['document']['thumbnails']['enabled']; @@ -177,4 +226,9 @@ private function isTextProcessingEnabled(): bool { return Config::getSystemConfiguration('assets')['document']['process_text']; } + + private function isPdfScanningEnabled(): bool + { + return Config::getSystemConfiguration('assets')['document']['scan_pdf']; + } }
models/Asset/Enum/PdfScanStatus.php+24 −0 added@@ -0,0 +1,24 @@ +<?php +declare(strict_types=1); + +/** + * Pimcore + * + * This source file is available under two different licenses: + * - GNU General Public License version 3 (GPLv3) + * - Pimcore Commercial License (PCL) + * Full copyright and license information is available in + * LICENSE.md which is distributed with this source code. + * + * @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org) + * @license http://www.pimcore.org/license GPLv3 and PCL + */ + +namespace Pimcore\Model\Asset\Enum; + +enum PdfScanStatus: string +{ + case SAFE = 'safe'; + case UNSAFE = 'unsafe'; + case IN_PROGRESS = 'inProgress'; +}
models/Element/Service.php+4 −0 modified@@ -731,6 +731,10 @@ public static function renewReferences(mixed $data, bool $initial = true, string return $data; } if (is_object($data)) { + if ($data instanceof \UnitEnum) { + return $data; + } + if ($data instanceof ElementInterface && !$initial) { return self::getElementById(self::getElementType($data), $data->getId()); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-jfxw-6c5v-c42fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-46722ghsaADVISORY
- github.com/pimcore/admin-ui-classic-bundle/commit/19fda2e86557c2ed4978316104de5ccdaa66d8b9ghsax_refsource_MISCWEB
- github.com/pimcore/admin-ui-classic-bundle/security/advisories/GHSA-jfxw-6c5v-c42fghsax_refsource_CONFIRMWEB
- github.com/pimcore/pimcore/commit/757375677dc83a44c6c22f26d97452cc5cda5d7cghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.