CVE-2023-47636 (published 2023-11-15T20:15Z)

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.\n

CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Weakness information:
cwe_id: CWE-209
cwe_name: Generation of Error Message Containing Sensitive Information
description: The product generates an error message that includes sensitive information about its environment, users, or associated data.
extended_description: The sensitive information may be valuable information on its own (such as a password), or it may be useful for launching other, more serious attacks. The error message may be created in different ways:
           
             self-generated: the source code explicitly constructs the error message and delivers it
	     externally-generated: the external environment, such as a language interpreter, handles the error and constructs its own message, whose contents are not under direct control by the programmer
	   
	 An attacker may use the contents of error messages to help launch another, more focused attack. For example, an attempt to exploit a path traversal weakness (CWE-22) might yield the full pathname of the installed application. In turn, this could be used to select the proper number of ".." sequences to navigate to the targeted file. An attack using SQL injection (CWE-89) might not initially succeed, but an error message could reveal the malformed query, which would expose query logic and possibly even passwords or other sensitive information used within the query.
         

Fix information:
repo_url: https://github.com/pimcore/admin-ui-classic-bundle
commit_details: repo_url: https://github.com/pimcore/admin-ui-classic-bundle
author: Divesh Pahuja
author_date: 2023-11-09 14:30:03+01:00
msg: 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
repository: repo_url: https://github.com/pimcore/admin-ui-classic-bundle
repo_name: pimcore/admin-ui-classic-bundle
repo_language: JavaScript
owner: pimcore
file_changes: filename: AssetHelperController.php
change_type: ModificationType.MODIFY
diff: @@ -26,8 +26,8 @@ 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;
@@ -722,17 +722,13 @@ 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'));
 
@@ -752,27 +748,37 @@ class AssetHelperController extends AdminAbstractController
 
         $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]);
     }
 
@@ -860,7 +866,7 @@ class AssetHelperController extends AdminAbstractController
     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 {
@@ -893,7 +899,7 @@ class AssetHelperController extends AdminAbstractController
     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 {

programming_language: PHP
filename: DataObjectHelperController.php
change_type: ModificationType.MODIFY
diff: @@ -26,9 +26,11 @@ 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;
@@ -1283,20 +1285,17 @@ 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';
 
@@ -1308,13 +1307,13 @@ class DataObjectHelperController extends AdminAbstractController
         $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 = [];
@@ -1340,56 +1339,72 @@ class DataObjectHelperController extends AdminAbstractController
 
         $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]);
     }
@@ -1412,7 +1427,7 @@ class DataObjectHelperController extends AdminAbstractController
     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 {
@@ -1445,7 +1460,7 @@ class DataObjectHelperController extends AdminAbstractController
     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 {

programming_language: PHP