CVE-2023-47636: Generation of Error Message Containing Sensitive Information (CWE-209)
value: The Pimcore Admin Classic Bundle provides a Backend UI for Pimcore. Full Path Disclosure (FPD) vulnerabilities enable the attacker to see the path to the webroot/file. e.g.: /home/omg/htdocs/file/. Certain vulnerabilities, such as using the load_file() (within a SQL Injection) query to view the page source, require the attacker to have the full path to the file they wish to view. In the case of pimcore, the fopen() function here doesn't have an error handle when the file doesn't exist on the server so the server response raises the full path "fopen(/var/www/html/var/tmp/export-{ uniqe id}.csv)". This issue has been patched in commit `10d178ef771` which has been included in release version 1.2.1. Users are advised to upgrade. There are no known workarounds for this vulnerability.

CWE description: The product generates an error message that includes sensitive information about its environment, users, or associated data.
CVSS v3 base score: 5.3
Published: 2023-11-15T20:15Z
Repository: https://github.com/pimcore/admin-ui-classic-bundle
Language: PHP
Files changed: src/Controller/Admin/Asset/AssetHelperController.php, src/Controller/Admin/DataObject/DataObjectHelperController.php
Commit date: 2023-11-09 14:30:03 +0100
Commit message: Grid Export - Catch unable to read file exception (#342)

* [DataObject] Grid Export - Catch unable to read file exception
* [Asset] Grid Export - Catch unable to read file exception
Diff: diff --git a/src/Controller/Admin/Asset/AssetHelperController.php b/src/Controller/Admin/Asset/AssetHelperController.php
index 330d18715d..0e50e3e9a8 100644
--- a/src/Controller/Admin/Asset/AssetHelperController.php
+++ b/src/Controller/Admin/Asset/AssetHelperController.php
@@ -24,12 +24,12 @@ use Pimcore\Bundle\AdminBundle\Helper\GridHelperService;
 use Pimcore\Bundle\AdminBundle\Model\GridConfig;
 use Pimcore\Bundle\AdminBundle\Model\GridConfigFavourite;
 use Pimcore\Bundle\AdminBundle\Model\GridConfigShare;
 use Pimcore\Bundle\AdminBundle\Tool;
 use Pimcore\Db;
+use Pimcore\File;
 use Pimcore\Loader\ImplementationLoader\Exception\UnsupportedException;
-use Pimcore\Localization\LocaleServiceInterface;
 use Pimcore\Logger;
 use Pimcore\Model\Asset;
 use Pimcore\Model\Element;
 use Pimcore\Model\Metadata;
 use Pimcore\Model\User;
@@ -720,21 +720,17 @@ class AssetHelperController extends AdminAbstractController
     }
 
     /**
      * @Route("/do-export", name="pimcore_admin_asset_assethelper_doexport", methods={"POST"})
      *
-     * @param Request $request
-     * @param LocaleServiceInterface $localeService
-     *
-     * @return JsonResponse
+     * @throws FilesystemException
      */
-    public function doExportAction(Request $request, LocaleServiceInterface $localeService): JsonResponse
+    public function doExportAction(Request $request): JsonResponse
     {
-        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
+        $fileHandle = File::getValidFilename($request->get('fileHandle'));
         $ids = $request->get('ids');
-        $settings = $request->get('settings');
-        $settings = json_decode($settings, true);
+        $settings = json_decode($request->get('settings'), true);
         $delimiter = $settings['delimiter'] ?? ';';
         $language = str_replace('default', '', $request->get('language'));
 
         $list = new Asset\Listing();
 
@@ -750,31 +746,41 @@ class AssetHelperController extends AdminAbstractController
 
         $addTitles = (bool) $request->get('initial');
 
         $csv = $this->getCsvData($request, $language, $list, $fields, $addTitles);
 
-        $storage = Storage::get('temp');
-        $csvFile = $this->getCsvFile($fileHandle);
+        try {
+            $storage = Storage::get('temp');
+            $csvFile = $this->getCsvFile($fileHandle);
 
-        $fileStream = $storage->readStream($csvFile);
+            $fileStream = $storage->readStream($csvFile);
 
-        $temp = tmpfile();
-        stream_copy_to_stream($fileStream, $temp, null, 0);
+            $temp = tmpfile();
+            stream_copy_to_stream($fileStream, $temp, null, 0);
 
-        $firstLine = true;
-        foreach ($csv as $line) {
-            if ($addTitles && $firstLine) {
-                $firstLine = false;
-                $line = implode($delimiter, $line) . "\r\n";
-                fwrite($temp, $line);
-            } else {
-                fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)) . "\r\n");
+            $firstLine = true;
+            foreach ($csv as $line) {
+                if ($addTitles && $firstLine) {
+                    $firstLine = false;
+                    $line = implode($delimiter, $line) . "\r\n";
+                    fwrite($temp, $line);
+                } else {
+                    fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)) . "\r\n");
+                }
             }
+            $storage->writeStream($csvFile, $temp);
+        } catch (UnableToReadFile $exception) {
+            Logger::err($exception->getMessage());
+
+            return $this->adminJson(
+                [
+                    'success' => false,
+                    'message' => sprintf('export file not found: %s', $fileHandle)
+                ]
+            );
         }
 
-        $storage->writeStream($csvFile, $temp);
-
         return $this->adminJson(['success' => true]);
     }
 
     public function encodeFunc(?string $value): string
     {
@@ -858,11 +864,11 @@ class AssetHelperController extends AdminAbstractController
      * @return Response
      */
     public function downloadCsvFileAction(Request $request): Response
     {
         $storage = Storage::get('temp');
-        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
+        $fileHandle = File::getValidFilename($request->get('fileHandle'));
         $csvFile = $this->getCsvFile($fileHandle);
 
         try {
             $csvData = $storage->read($csvFile);
             $response = new Response($csvData);
@@ -891,11 +897,11 @@ class AssetHelperController extends AdminAbstractController
      * @return BinaryFileResponse
      */
     public function downloadXlsxFileAction(Request $request, GridHelperService $gridHelperService): BinaryFileResponse
     {
         $storage = Storage::get('temp');
-        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
+        $fileHandle = File::getValidFilename($request->get('fileHandle'));
         $csvFile = $this->getCsvFile($fileHandle);
 
         try {
             return $gridHelperService->createXlsxExportFile($storage, $fileHandle, $csvFile);
         } catch (\Exception | FilesystemException | UnableToReadFile $exception) {
diff --git a/src/Controller/Admin/DataObject/DataObjectHelperController.php b/src/Controller/Admin/DataObject/DataObjectHelperController.php
index 7325c781e5..c897573035 100644
--- a/src/Controller/Admin/DataObject/DataObjectHelperController.php
+++ b/src/Controller/Admin/DataObject/DataObjectHelperController.php
@@ -24,13 +24,15 @@ use Pimcore\Bundle\AdminBundle\Helper\GridHelperService;
 use Pimcore\Bundle\AdminBundle\Model\GridConfig;
 use Pimcore\Bundle\AdminBundle\Model\GridConfigFavourite;
 use Pimcore\Bundle\AdminBundle\Model\GridConfigShare;
 use Pimcore\Config;
 use Pimcore\Db;
+use Pimcore\File;
 use Pimcore\Localization\LocaleServiceInterface;
 use Pimcore\Logger;
 use Pimcore\Model\DataObject;
+use Pimcore\Model\DataObject\Listing;
 use Pimcore\Model\User;
 use Pimcore\Security\SecurityHelper;
 use Pimcore\Tool;
 use Pimcore\Tool\Storage;
 use Pimcore\Version;
@@ -1281,24 +1283,21 @@ class DataObjectHelperController extends AdminAbstractController
     }
 
     /**
      * @Route("/do-export", name="doexport", methods={"POST"})
      *
-     * @param Request $request
-     * @param LocaleServiceInterface $localeService
-     * @param EventDispatcherInterface $eventDispatcher
-     *
-     * @return JsonResponse
-     *
-     * @throws \Exception
+     * @throws \Exception|FilesystemException
      */
-    public function doExportAction(Request $request, LocaleServiceInterface $localeService, EventDispatcherInterface $eventDispatcher): JsonResponse
+    public function doExportAction(
+        Request $request,
+        LocaleServiceInterface $localeService,
+        EventDispatcherInterface $eventDispatcher
+    ): JsonResponse
     {
-        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
+        $fileHandle = File::getValidFilename($request->get('fileHandle'));
         $ids = $request->get('ids');
-        $settings = $request->get('settings');
-        $settings = json_decode($settings, true);
+        $settings = json_decode($request->get('settings'), true);
         $delimiter = $settings['delimiter'] ?? ';';
         $header = $settings['header'] ?? 'title';
 
         $allParams = array_merge($request->request->all(), $request->query->all());
 
@@ -1306,17 +1305,17 @@ class DataObjectHelperController extends AdminAbstractController
         DataObject\Concrete::setGetInheritedValues($enableInheritance);
 
         $class = DataObject\ClassDefinition::getById($request->get('classId'));
 
         if (!$class) {
-            throw new \Exception('No class definition found');
+            throw new \InvalidArgumentException('No class definition found');
         }
 
         $className = $class->getName();
         $listClass = '\\Pimcore\\Model\\DataObject\\' . ucfirst($className) . '\\Listing';
 
-        /** @var \Pimcore\Model\DataObject\Listing $list */
+        /** @var Listing $list */
         $list = new $listClass();
 
         $quotedIds = [];
         foreach ($ids as $id) {
             $quotedIds[] = $list->quote($id);
@@ -1338,60 +1337,76 @@ class DataObjectHelperController extends AdminAbstractController
 
         $addTitles = (bool) $request->get('initial');
 
         $requestedLanguage = $this->extractLanguage($request);
 
-        $contextFromRequest = $request->get('context');
-        if ($contextFromRequest) {
-            $contextFromRequest = json_decode($contextFromRequest, true);
-        }
-
         $context = [
             'source' => 'pimcore-export',
         ];
 
-        if (is_array($contextFromRequest)) {
+        $contextFromRequest = $request->get('context');
+        if ($contextFromRequest) {
+            $contextFromRequest = json_decode($contextFromRequest, true);
             $context = array_merge($context, $contextFromRequest);
         }
 
-        $csv = DataObject\Service::getCsvData($requestedLanguage, $localeService, $list, $fields, $header, $addTitles, $context);
+        $csv = DataObject\Service::getCsvData(
+            $requestedLanguage,
+            $localeService,
+            $list,
+            $fields,
+            $header,
+            $addTitles,
+            $context
+        );
 
-        $storage = Storage::get('temp');
-        $csvFile = $this->getCsvFile($fileHandle);
-
-        $fileStream = $storage->readStream($csvFile);
-
-        $temp = tmpfile();
-        stream_copy_to_stream($fileStream, $temp, null, 0);
-
-        $firstLine = true;
+        try {
+            $storage = Storage::get('temp');
+            $csvFile = $this->getCsvFile($fileHandle);
 
-        if ($request->get('initial') && $header === 'no_header') {
-            array_shift($csv);
-            $firstLine = false;
-        }
+            $fileStream = $storage->readStream($csvFile);
 
-        $lineCount = count($csv);
+            $temp = tmpfile();
+            stream_copy_to_stream($fileStream, $temp, null, 0);
 
-        if (!$addTitles && $lineCount > 0) {
-            fwrite($temp, "\r\n");
-        }
+            $firstLine = true;
 
-        for ($i = 0; $i < $lineCount; $i++) {
-            $line = $csv[$i];
-            if ($addTitles && $firstLine) {
+            if ($request->get('initial') && $header === 'no_header') {
+                array_shift($csv);
                 $firstLine = false;
-                $line = implode($delimiter, $line);
-                fwrite($temp, $line);
-            } else {
-                fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)));
             }
-            if ($i < $lineCount - 1) {
+
+            $lineCount = count($csv);
+
+            if (!$addTitles && $lineCount > 0) {
                 fwrite($temp, "\r\n");
             }
+
+            for ($i = 0; $i < $lineCount; $i++) {
+                $line = $csv[$i];
+                if ($addTitles && $firstLine) {
+                    $firstLine = false;
+                    $line = implode($delimiter, $line);
+                    fwrite($temp, $line);
+                } else {
+                    fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)));
+                }
+                if ($i < $lineCount - 1) {
+                    fwrite($temp, "\r\n");
+                }
+            }
+            $storage->writeStream($csvFile, $temp);
+        } catch (UnableToReadFile $exception) {
+            Logger::err($exception->getMessage());
+
+            return $this->adminJson(
+                [
+                    'success' => false,
+                    'message' => sprintf('export file not found: %s', $fileHandle)
+                ]
+            );
         }
-        $storage->writeStream($csvFile, $temp);
 
         return $this->adminJson(['success' => true]);
     }
 
     public function encodeFunc(string $value): string
@@ -1410,11 +1425,11 @@ class DataObjectHelperController extends AdminAbstractController
      * @return Response
      */
     public function downloadCsvFileAction(Request $request): Response
     {
         $storage = Storage::get('temp');
-        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
+        $fileHandle = File::getValidFilename($request->get('fileHandle'));
         $csvFile = $this->getCsvFile($fileHandle);
 
         try {
             $csvData = $storage->read($csvFile);
             $response = new Response($csvData);
@@ -1443,11 +1458,11 @@ class DataObjectHelperController extends AdminAbstractController
      * @return BinaryFileResponse
      */
     public function downloadXlsxFileAction(Request $request, GridHelperService $gridHelperService): BinaryFileResponse
     {
         $storage = Storage::get('temp');
-        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
+        $fileHandle = File::getValidFilename($request->get('fileHandle'));
         $csvFile = $this->getCsvFile($fileHandle);
 
         try {
             return $gridHelperService->createXlsxExportFile($storage, $fileHandle, $csvFile);
         } catch (\Exception | FilesystemException | UnableToReadFile $exception) {

Vulnerable code: use Pimcore\Localization\LocaleServiceInterface;
     * @param Request $request
     * @param LocaleServiceInterface $localeService
     *
     * @return JsonResponse
    public function doExportAction(Request $request, LocaleServiceInterface $localeService): JsonResponse
        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
        $settings = $request->get('settings');
        $settings = json_decode($settings, true);
        $storage = Storage::get('temp');
        $csvFile = $this->getCsvFile($fileHandle);
        $fileStream = $storage->readStream($csvFile);
        $temp = tmpfile();
        stream_copy_to_stream($fileStream, $temp, null, 0);
        $firstLine = true;
        foreach ($csv as $line) {
            if ($addTitles && $firstLine) {
                $firstLine = false;
                $line = implode($delimiter, $line) . "\r\n";
                fwrite($temp, $line);
            } else {
                fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)) . "\r\n");
        $storage->writeStream($csvFile, $temp);

        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
     * @param Request $request
     * @param LocaleServiceInterface $localeService
     * @param EventDispatcherInterface $eventDispatcher
     *
     * @return JsonResponse
     *
     * @throws \Exception
    public function doExportAction(Request $request, LocaleServiceInterface $localeService, EventDispatcherInterface $eventDispatcher): JsonResponse
        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
        $settings = $request->get('settings');
        $settings = json_decode($settings, true);
            throw new \Exception('No class definition found');
        /** @var \Pimcore\Model\DataObject\Listing $list */
        $contextFromRequest = $request->get('context');
        if ($contextFromRequest) {
            $contextFromRequest = json_decode($contextFromRequest, true);
        }

        if (is_array($contextFromRequest)) {
        $csv = DataObject\Service::getCsvData($requestedLanguage, $localeService, $list, $fields, $header, $addTitles, $context);
        $storage = Storage::get('temp');
        $csvFile = $this->getCsvFile($fileHandle);

        $fileStream = $storage->readStream($csvFile);

        $temp = tmpfile();
        stream_copy_to_stream($fileStream, $temp, null, 0);

        $firstLine = true;
        if ($request->get('initial') && $header === 'no_header') {
            array_shift($csv);
            $firstLine = false;
        }
        $lineCount = count($csv);
        if (!$addTitles && $lineCount > 0) {
            fwrite($temp, "\r\n");
        }
        for ($i = 0; $i < $lineCount; $i++) {
            $line = $csv[$i];
            if ($addTitles && $firstLine) {
                $line = implode($delimiter, $line);
                fwrite($temp, $line);
            } else {
                fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)));
            if ($i < $lineCount - 1) {
        $storage->writeStream($csvFile, $temp);
        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
        $fileHandle = \Pimcore\File::getValidFilename($request->get('fileHandle'));
Fixed code: use Pimcore\File;
     * @throws FilesystemException
    public function doExportAction(Request $request): JsonResponse
        $fileHandle = File::getValidFilename($request->get('fileHandle'));
        $settings = json_decode($request->get('settings'), true);
        try {
            $storage = Storage::get('temp');
            $csvFile = $this->getCsvFile($fileHandle);
            $fileStream = $storage->readStream($csvFile);
            $temp = tmpfile();
            stream_copy_to_stream($fileStream, $temp, null, 0);
            $firstLine = true;
            foreach ($csv as $line) {
                if ($addTitles && $firstLine) {
                    $firstLine = false;
                    $line = implode($delimiter, $line) . "\r\n";
                    fwrite($temp, $line);
                } else {
                    fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)) . "\r\n");
                }
            $storage->writeStream($csvFile, $temp);
        } catch (UnableToReadFile $exception) {
            Logger::err($exception->getMessage());

            return $this->adminJson(
                [
                    'success' => false,
                    'message' => sprintf('export file not found: %s', $fileHandle)
                ]
            );
        $fileHandle = File::getValidFilename($request->get('fileHandle'));
        $fileHandle = File::getValidFilename($request->get('fileHandle'));
use Pimcore\File;
use Pimcore\Model\DataObject\Listing;
     * @throws \Exception|FilesystemException
    public function doExportAction(
        Request $request,
        LocaleServiceInterface $localeService,
        EventDispatcherInterface $eventDispatcher
    ): JsonResponse
        $fileHandle = File::getValidFilename($request->get('fileHandle'));
        $settings = json_decode($request->get('settings'), true);
            throw new \InvalidArgumentException('No class definition found');
        /** @var Listing $list */
        $contextFromRequest = $request->get('context');
        if ($contextFromRequest) {
            $contextFromRequest = json_decode($contextFromRequest, true);
        $csv = DataObject\Service::getCsvData(
            $requestedLanguage,
            $localeService,
            $list,
            $fields,
            $header,
            $addTitles,
            $context
        );
        try {
            $storage = Storage::get('temp');
            $csvFile = $this->getCsvFile($fileHandle);
            $fileStream = $storage->readStream($csvFile);
            $temp = tmpfile();
            stream_copy_to_stream($fileStream, $temp, null, 0);
            $firstLine = true;
            if ($request->get('initial') && $header === 'no_header') {
                array_shift($csv);

            $lineCount = count($csv);

            if (!$addTitles && $lineCount > 0) {

            for ($i = 0; $i < $lineCount; $i++) {
                $line = $csv[$i];
                if ($addTitles && $firstLine) {
                    $firstLine = false;
                    $line = implode($delimiter, $line);
                    fwrite($temp, $line);
                } else {
                    fwrite($temp, implode($delimiter, array_map([$this, 'encodeFunc'], $line)));
                }
                if ($i < $lineCount - 1) {
                    fwrite($temp, "\r\n");
                }
            }
            $storage->writeStream($csvFile, $temp);
        } catch (UnableToReadFile $exception) {
            Logger::err($exception->getMessage());

            return $this->adminJson(
                [
                    'success' => false,
                    'message' => sprintf('export file not found: %s', $fileHandle)
                ]
            );
        $fileHandle = File::getValidFilename($request->get('fileHandle'));
        $fileHandle = File::getValidFilename($request->get('fileHandle'));