CVE-2023-30849 (published 2023-04-27T16:15Z)

Pimcore is an open source data and experience management platform. Prior to version 10.5.21, A SQL injection vulnerability exists in the translation export API. Users should update to version 10.5.21 to receive a patch or, as a workaround, or apply the patch manually.

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

Weakness information:
cwe_id: CWE-89
cwe_name: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/pimcore/pimcore
commit_details: repo_url: https://github.com/pimcore/pimcore
author: Matthias Schuhmayer
author_date: 2023-04-20 10:07:32+02:00
msg: [Security] Fix Admin Translation Export SQL Injection (#14968)

* Fix admin translation export sql injection

* Change to setConditon

* Add SyntaxError Exception, replace sql comments

* Remove empty line

* Use const, remove unecessary line
repository: repo_url: https://github.com/pimcore/pimcore
repo_name: pimcore/pimcore
description: Core Framework for the Open Source Data & Experience Management Platform (PIM, MDM, CDP, DAM, DXP/CMS & Digital Commerce)
repo_language: PHP
owner: pimcore
file_changes: filename: TranslationController.php
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,7 @@
 
 namespace Pimcore\Bundle\AdminBundle\Controller\Admin;
 
+use Doctrine\DBAL\Exception\SyntaxErrorException;
 use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
 use Pimcore\Bundle\AdminBundle\Controller\AdminController;
 use Pimcore\File;
@@ -49,6 +50,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
  */
 class TranslationController extends AdminController
 {
+    protected const PLACEHOLDER_NAME = 'placeHolder';
+
     /**
      * @Route("/import", name="pimcore_admin_translation_import", methods={"POST"})
      *
@@ -183,9 +186,9 @@ class TranslationController extends AdminController
         $list->setOrder('asc');
         $list->setOrderKey($tableName . '.key', false);
 
-        $condition = $this->getGridFilterCondition($request, $tableName, false, $admin);
-        if ($condition) {
-            $list->setCondition($condition);
+        $conditions = $this->getGridFilterCondition($request, $tableName, false, $admin);
+        if (!empty($conditions)) {
+            $list->setCondition($conditions['condition'], $conditions['params']);
         }
 
         $filters = $this->getGridFilterCondition($request, $tableName, true, $admin);
@@ -195,7 +198,12 @@ class TranslationController extends AdminController
         }
 
         $this->extendTranslationQuery($joins, $list, $tableName, $filters);
-        $list->load();
+
+        try {
+            $list->load();
+        } catch (SyntaxErrorException $syntaxErrorException) {
+            throw new \InvalidArgumentException('Check your arguments.');
+        }
 
         $translations = [];
         $translationObjects = $list->getTranslations();
@@ -460,19 +468,24 @@ class TranslationController extends AdminController
             $list->setLimit($request->get('limit'));
             $list->setOffset($request->get('start'));
 
-            $condition = $this->getGridFilterCondition($request, $tableName, false, $admin);
+            $conditions = $this->getGridFilterCondition($request, $tableName, false, $admin);
             $filters = $this->getGridFilterCondition($request, $tableName, true, $admin);
 
             if ($filters) {
                 $joins = array_merge($joins, $filters['joins']);
             }
-            if ($condition) {
-                $list->setCondition($condition);
+
+            if (!empty($conditions)) {
+                $list->setCondition($conditions['condition'], $conditions['params']);
             }
 
             $this->extendTranslationQuery($joins, $list, $tableName, $filters);
 
-            $list->load();
+            try {
+                $list->load();
+            } catch (SyntaxErrorException $syntaxErrorException) {
+                throw new \InvalidArgumentException('Check your arguments.');
+            }
 
             $translations = [];
             $searchString = $request->get('searchString');
@@ -574,11 +587,12 @@ class TranslationController extends AdminController
      * @param Request $request
      * @param string $tableName
      * @param bool $languageMode
-     *
-     * @return array|null|string
+     * @param bool $admin
+     * @return array
      */
     protected function getGridFilterCondition(Request $request, $tableName, $languageMode = false, $admin = false)
     {
+        $placeHolderCount = 0;
         $joins = [];
         $conditions = [];
         $validLanguages = $admin ? Tool\Admin::getLanguages() : $this->getAdminUser()->getAllowedLanguagesForViewingWebsiteTranslations();
@@ -592,6 +606,7 @@ class TranslationController extends AdminController
             $operatorField = 'operator';
 
             $filters = $this->decodeJson($filterJson);
+
             foreach ($filters as $filter) {
                 $operator = '=';
                 $field = null;
@@ -601,6 +616,7 @@ class TranslationController extends AdminController
                 if (in_array(ltrim($fieldname, '_'), $validLanguages)) {
                     $fieldname = ltrim($fieldname, '_');
                 }
+                $fieldname = str_replace('--', '', $fieldname);
 
                 if (!$languageMode && in_array($fieldname, $validLanguages)
                     || $languageMode && !in_array($fieldname, $validLanguages)) {
@@ -641,31 +657,47 @@ class TranslationController extends AdminController
                             'language' => $fieldname,
                         ];
                     } else {
-                        $conditionFilters[] = $condition;
+                        $placeHolderName = self::PLACEHOLDER_NAME . $placeHolderCount;
+                        $placeHolderCount++;
+                        $conditionFilters[] = [
+                            'condition' => $field . ' ' . $operator . ' :' . $placeHolderName,
+                            'field' => $placeHolderName,
+                            'value' => $value
+                        ];
                     }
                 }
             }
         }
 
         if ($request->get('searchString')) {
-            $filterTerm = $db->quote('%' . mb_strtolower($request->get('searchString')) . '%');
-            $conditionFilters[] = '(lower(' . $tableName . '.key) LIKE ' . $filterTerm . ' OR lower(' . $tableName . '.text) LIKE ' . $filterTerm . ')';
+            $conditionFilters[] = [
+                'condition' => '(lower(' . $tableName . '.key) LIKE :filterTerm OR lower(' . $tableName . '.text) LIKE :filterTerm)',
+                'field' => 'filterTerm',
+                'value' => '%' . mb_strtolower($request->get('searchString')) . '%'
+            ];
         }
 
         if ($languageMode) {
-            $result = [
+            return [
                 'joins' => $joins,
                 'conditions' => $conditions,
             ];
+        }
 
-            return $result;
-        } else {
-            if (!empty($conditionFilters)) {
-                return implode(' AND ', $conditionFilters);
+        if(!empty($conditionFilters)) {
+            $conditions = [];
+            $params = [];
+            foreach($conditionFilters as $conditionFilter) {
+                $conditions[] = $conditionFilter['condition'];
+                $params[$conditionFilter['field']] = $conditionFilter['value'];
             }
 
-            return null;
+            $conditionFilters = [
+                'condition' => implode(' AND ', $conditions),
+                'params' => $params
+            ];
         }
+        return $conditionFilters;
     }
 
     /**

programming_language: PHP