Pimcore Path Traversal Vulnerability in AdminBundle/Controller/Reports/CustomReportController.php
Description
Pimcore is an open source data and experience management platform. Versions of Pimcore prior to 10.5.18 are vulnerable to path traversal. The impact of this path traversal and arbitrary extension is limited to creation of arbitrary files and appending data to existing files. When combined with the SQL Injection, the exported data RESTRICTED DIFFUSION 9 / 9 can be controlled and a webshell can be uploaded. Attackers can use that to execute arbitrary PHP code on the server with the permissions of the webserver. Users may upgrade to version 10.5.18 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/pimcorePackagist | < 10.5.18 | 10.5.18 |
Affected products
1Patches
2f1d904094700[Security] XSS in Classification Store of Data Objects module (#14933)
6 files changed · +40 −14
bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php+4 −3 modified@@ -24,6 +24,7 @@ use Pimcore\Model\Translation; use Pimcore\Model\Translation\Listing; use Pimcore\Model\User; +use Pimcore\Security\SecurityHelper; use Pimcore\Tool\Admin; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -127,7 +128,7 @@ public function deleteGroupAction(Request $request): JsonResponse */ public function createGroupAction(Request $request): JsonResponse { - $name = $request->get('name'); + $name = SecurityHelper::getStringWithoutControlChars($request->get('name')); $storeId = $request->get('storeId'); $config = Classificationstore\GroupConfig::getByName($name, $storeId); @@ -154,7 +155,7 @@ public function createGroupAction(Request $request): JsonResponse */ public function createStoreAction(Request $request): JsonResponse { - $name = $request->get('name'); + $name = SecurityHelper::getStringWithoutControlChars($request->get('name')); $config = Classificationstore\StoreConfig::getByName($name); @@ -180,7 +181,7 @@ public function createStoreAction(Request $request): JsonResponse */ public function createCollectionAction(Request $request): JsonResponse { - $name = $request->get('name'); + $name = SecurityHelper::getStringWithoutControlChars($request->get('name')); $storeId = $request->get('storeId'); $config = Classificationstore\CollectionConfig::getByName($name, $storeId);
bundles/AdminBundle/Resources/public/js/pimcore/helpers.js+4 −0 modified@@ -3278,3 +3278,7 @@ pimcore.helpers.treeDragDropValidate = function (node, oldParent, newParent) { return true; }; + +pimcore.helpers.getStringWithoutControlChars = function (text) { + return text.replace(/[<>"'`!?/\\%$(){};,:|=]/gi, ''); +};
bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/collectionsPanel.js+3 −4 modified@@ -141,7 +141,7 @@ pimcore.object.classificationstore.collectionsPanel = Class.create({ var colId = data.data.colId; var groupId = data.data.groupId; - Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_collection_relation'), data.data.groupName), function(btn) { + Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_collection_relation'), pimcore.helpers.getStringWithoutControlChars(data.data.groupName)), function(btn) { if (btn == 'yes') { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_deletecollectionrelation'), @@ -315,7 +315,7 @@ pimcore.object.classificationstore.collectionsPanel = Class.create({ this.relationsGrid.hide(); this.relationsPanel.disable(); - Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_collection'), data.data.name), function(btn) { + Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_collection'), pimcore.helpers.getStringWithoutControlChars(data.data.name)), function(btn) { if (btn == 'yes') { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_deletecollection'), @@ -410,8 +410,7 @@ pimcore.object.classificationstore.collectionsPanel = Class.create({ }, addFieldComplete: function (button, value, object) { - - value = value.trim(); + value = pimcore.helpers.getStringWithoutControlChars(value).trim(); if (button == "ok" && value.length > 1) { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_createcollection'),
bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/groupsPanel.js+3 −4 modified@@ -148,7 +148,7 @@ pimcore.object.classificationstore.groupsPanel = Class.create({ var keyId = data.data.keyId; var groupId = data.data.groupId; - Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_relation'), data.data.keyName), function(btn) { + Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_relation'), pimcore.helpers.getStringWithoutControlChars(data.data.keyName)), function(btn) { if (btn == 'yes') { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_deleterelation'), @@ -319,7 +319,7 @@ pimcore.object.classificationstore.groupsPanel = Class.create({ var data = grid.getStore().getAt(rowIndex); var id = data.data.id; - Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_group'), data.data.name), function(btn) { + Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_group'), pimcore.helpers.getStringWithoutControlChars(data.data.name)), function(btn) { if (btn == 'yes') { //necessary for aborting all pending proxy requests @@ -419,8 +419,7 @@ pimcore.object.classificationstore.groupsPanel = Class.create({ }, addFieldComplete: function (button, value, object) { - - value = value.trim(); + value = pimcore.helpers.getStringWithoutControlChars(value).trim(); if (button == "ok" && value.length > 1) { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_creategroup'),
bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/propertiesPanel.js+2 −3 modified@@ -198,7 +198,7 @@ pimcore.object.classificationstore.propertiespanel = Class.create({ var data = grid.getStore().getAt(rowIndex); var id = data.data.id; - Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_property'), data.data.name), function(btn) { + Ext.Msg.confirm(t('delete'), sprintf(t('delete_message_advanced'), t('classificationstore_property'), pimcore.helpers.getStringWithoutControlChars(data.data.name)), function(btn) { if (btn == 'yes') { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_deleteproperty'), @@ -327,8 +327,7 @@ pimcore.object.classificationstore.propertiespanel = Class.create({ }, addFieldComplete: function (button, value, object) { - - value = value.trim(); + value = pimcore.helpers.getStringWithoutControlChars(value).trim(); if (button == "ok" && value.length > 1) { Ext.Ajax.request({ url: Routing.generate('pimcore_admin_dataobject_classificationstore_addproperty'),
lib/Security/SecurityHelper.php+24 −0 added@@ -0,0 +1,24 @@ +<?php + +/** + * 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\Security; + +class SecurityHelper +{ + public static function getStringWithoutControlChars(string $text): string + { + return preg_replace('[\\\\<>"\'`!?/%$(){};,:|=]','', $text); + } +}
7f788fa44bc1[Bug] createCSV in CustomReportsController (#14498)
1 file changed · +11 −2
bundles/AdminBundle/Controller/Reports/CustomReportController.php+11 −2 modified@@ -24,6 +24,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; /** * @Route("/custom-report") @@ -410,6 +411,14 @@ public function chartAction(Request $request) ]); } + protected function getTemporaryFileFromFileName(string $exportFileName): string { + $exportFileName = basename($exportFileName); + if(!str_ends_with($exportFileName, ".csv")) { + throw new InvalidArgumentException($exportFileName . " is not a valid csv file."); + } + return PIMCORE_SYSTEM_TEMP_DIRECTORY . '/' . $exportFileName; + } + /** * @Route("/create-csv", name="pimcore_admin_reports_customreport_createcsv", methods={"GET"}) * @@ -459,7 +468,7 @@ public function createCsvAction(Request $request) $exportFile = PIMCORE_SYSTEM_TEMP_DIRECTORY . '/report-export-' . uniqid() . '.csv'; @unlink($exportFile); } else { - $exportFile = PIMCORE_SYSTEM_TEMP_DIRECTORY.'/'.$exportFile; + $exportFile = $this->getTemporaryFileFromFileName($exportFile); } $fp = fopen($exportFile, 'a'); @@ -497,7 +506,7 @@ public function downloadCsvAction(Request $request) { $this->checkPermission('reports'); if ($exportFile = $request->get('exportFile')) { - $exportFile = PIMCORE_SYSTEM_TEMP_DIRECTORY . '/' . basename($exportFile); + $exportFile = $this->getTemporaryFileFromFileName($exportFile); $response = new BinaryFileResponse($exportFile); $response->headers->set('Content-Type', 'text/csv; charset=UTF-8'); $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'export.csv');
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
6- github.com/advisories/GHSA-g2mc-fqqc-hxg3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-30855ghsaADVISORY
- github.com/pimcore/pimcore/commit/7f788fa44bc18bc1c9182c25e26b770a1d30b62f.patchghsaWEB
- github.com/pimcore/pimcore/commit/f1d904094700b513c4756904fa2b1e19d08d890e.patchghsax_refsource_MISCWEB
- github.com/pimcore/pimcore/pull/14498ghsax_refsource_MISCWEB
- github.com/pimcore/pimcore/security/advisories/GHSA-g2mc-fqqc-hxg3ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.