VYPR
Moderate severityNVD Advisory· Published May 8, 2023· Updated Jan 29, 2025

Pimcore Path Traversal Vulnerability in AdminBundle/Controller/Reports/CustomReportController.php

CVE-2023-30855

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.

PackageAffected versionsPatched versions
pimcore/pimcorePackagist
< 10.5.1810.5.18

Affected products

1

Patches

2
f1d904094700

[Security] XSS in Classification Store of Data Objects module (#14933)

https://github.com/pimcore/pimcorerobertSt7Apr 18, 2023via ghsa
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)

https://github.com/pimcore/pimcoremcop1Mar 1, 2023via ghsa
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

News mentions

0

No linked articles in our index yet.