CVE-2021-4139: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: pimcore is vulnerable to Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 9
Published: 2021-12-21T13:15Z
Repository: https://github.com/pimcore/pimcore
Language: JavaScript
Files changed: bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php, bundles/AdminBundle/Resources/public/js/pimcore/helpers/generic-grid.js, bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/collectionsPanel.js, bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/groupsPanel.js, bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/propertiesPanel.js, bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/storeTree.js
Commit date: 2021-12-21 09:15:58 +0100
Commit message: [Classification Store] Properly escape values on grids & titles

Diff: diff --git a/bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php b/bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php
index e14b9d72a58..1f536f3a9d7 100644
--- a/bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php
+++ b/bundles/AdminBundle/Controller/Admin/DataObject/ClassificationstoreController.php
@@ -1474,23 +1474,23 @@ class ClassificationstoreController extends AdminController implements KernelCon
         $list = new Classificationstore\StoreConfig\Listing();
         $list = $list->load();
         foreach ($list as $item) {
             $resultItem = [
                 'id' => $item->getId(),
-                'text' => $item->getName(),
+                'text' => htmlspecialchars($item->getName(), ENT_QUOTES),
                 'expandable' => false,
                 'leaf' => true,
                 'expanded' => true,
-                'description' => $item->getDescription(),
+                'description' => htmlspecialchars($item->getDescription(), ENT_QUOTES),
                 'iconCls' => 'pimcore_icon_classificationstore',
             ];
 
             $resultItem['qtitle'] = 'ID: ' . $item->getId();
 
             if ($item->getDescription()) {
             }
-            $resultItem['qtip'] = $item->getDescription() ? $item->getDescription() : ' ';
+            $resultItem['qtip'] = $item->getDescription() ? htmlspecialchars($item->getDescription(), ENT_QUOTES) : ' ';
             $result[] = $resultItem;
         }
 
         return $this->adminJson($result);
     }
diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/helpers/generic-grid.js b/bundles/AdminBundle/Resources/public/js/pimcore/helpers/generic-grid.js
index ac4d60f5669..a237def5f9b 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/helpers/generic-grid.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/helpers/generic-grid.js
@@ -136,7 +136,7 @@ pimcore.helpers.grid.buildDefaultPagingToolbar = function (store, options) {
 
     return pagingtoolbar;
 };
 
 pimcore.helpers.grid.getTranslationColumnRenderer = function (value, metaData, record, rowIndex, colIndex, store) {
-    return t(value);
+    return Ext.util.Format.htmlEncode(t(value));
 };
diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/collectionsPanel.js b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/collectionsPanel.js
index a31c90d4ce6..390f56a2e0e 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/collectionsPanel.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/collectionsPanel.js
@@ -114,12 +114,14 @@ pimcore.object.classificationstore.collectionsPanel = Class.create({
                 }
             ]
         });
 
         gridColumns.push({text: t("group_id"), flex: 60, sortable: true, dataIndex: 'groupId', filter: 'string'});
-        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'groupName', filter: 'string'});
-        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'groupDescription', filter: 'string'});
+        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'groupName', filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
+        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'groupDescription', filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
 
         gridColumns.push({text: t('sorter'), width: 150, sortable: true, dataIndex: 'sorter',
             tooltip: t("classificationstore_tooltip_sorter"),
             editor: new Ext.form.NumberField()
         });
@@ -262,12 +264,14 @@ pimcore.object.classificationstore.collectionsPanel = Class.create({
 
         var gridColumns = [];
 
         //gridColumns.push({text: t("store"), flex: 60, sortable: true, dataIndex: 'storeId', filter: 'string'});
         gridColumns.push({text: "ID", flex: 60, sortable: true, dataIndex: 'id', filter: 'string'});
-        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string'});
-        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string'});
+        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
+        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
 
         var dateRenderer =  function(d) {
             if (d !== undefined) {
                 var date = new Date(d * 1000);
                 return Ext.Date.format(date, "Y-m-d H:i:s");
@@ -357,11 +361,11 @@ pimcore.object.classificationstore.collectionsPanel = Class.create({
             listeners: {
                 selectionchange: function(rowModel, selected, eOpts ) {
                     if (selected.length > 0) {
                         var record = selected[0];
                         var collectionId = record.data.id;
-                        var collectionName = record.data.name;
+                        var collectionName = Ext.util.Format.htmlEncode(record.data.name);
 
                         this.collectionId = collectionId;
 
                         this.relationsPanel.setTitle(t("relations") + " - " + t("collection") + " " + record.data.id + " - " + collectionName);
                         this.relationsPanel.enable();
diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/groupsPanel.js b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/groupsPanel.js
index f8551e53b6e..77b715c8b24 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/groupsPanel.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/groupsPanel.js
@@ -117,12 +117,14 @@ pimcore.object.classificationstore.groupsPanel = Class.create({
             ]
         });
 
 
         gridColumns.push({text: t("key_id"), flex: 60, sortable: true, dataIndex: 'keyId', filter: 'string'});
-        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'keyName', filter: 'string'});
-        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'keyDescription', filter: 'string'});
+        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'keyName', filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
+        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'keyDescription', filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
 
         gridColumns.push(mandatoryCheck);
         gridColumns.push({text: t('sorter'), width: 150, sortable: true, dataIndex: 'sorter',
             tooltip: t("classificationstore_tooltip_sorter"),
             editor: new Ext.form.NumberField()
@@ -267,12 +269,14 @@ pimcore.object.classificationstore.groupsPanel = Class.create({
         var gridColumns = [];
 
         //gridColumns.push({text: t("store"), width: 60, sortable: true, dataIndex: 'storeId', filter: 'string'});
         gridColumns.push({text: "ID", width: 60, sortable: true, dataIndex: 'id', filter: 'string'});
         gridColumns.push({text: t("parent_id"), width: 160, sortable: true, dataIndex: 'parentId', hidden: true, editor: new Ext.form.TextField({})});
-        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string'});
-        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string'});
+        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
+        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
 
         var dateRenderer =  function(d) {
             if (d !== undefined) {
                 var date = new Date(d * 1000);
                 return Ext.Date.format(date, "Y-m-d H:i:s");
@@ -363,11 +367,11 @@ pimcore.object.classificationstore.groupsPanel = Class.create({
 
                 selectionchange: function(rowModel, selected, eOpts ) {
                     if (selected.length > 0) {
                         var record = selected[0];
                         var groupId = record.data.id;
-                        var groupName = record.data.name;
+                        var groupName = Ext.util.Format.htmlEncode(record.data.name);
 
                         this.groupId = groupId;
 
                         this.relationsPanel.setTitle(t("relations") + " - " + t("group") + " " + record.data.id + " - " + groupName);
                         this.relationsPanel.enable();
diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/propertiesPanel.js b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/propertiesPanel.js
index 5bbd3b4038f..9f4242b9f2b 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/propertiesPanel.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/propertiesPanel.js
@@ -123,17 +123,20 @@ pimcore.object.classificationstore.propertiespanel = Class.create({
                 text: t("name"),
                 width: 200,
                 sortable: true,
                 dataIndex: 'name',
                 filter: 'string',
-                editor: new Ext.form.TextField({})
+                editor: new Ext.form.TextField({}),
+                renderer: Ext.util.Format.htmlEncode
             }
 
         );
 
-        gridColumns.push({text: t("title"), width: 200, sortable: false, dataIndex: 'title',editor: new Ext.form.TextField({}), filter: 'string'});
-        gridColumns.push({text: t("description"), width: 300, sortable: true, dataIndex: 'description',editor: new Ext.form.TextField({}), filter: 'string'});
+        gridColumns.push({text: t("title"), width: 200, sortable: false, dataIndex: 'title',editor: new Ext.form.TextField({}), filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
+        gridColumns.push({text: t("description"), width: 300, sortable: true, dataIndex: 'description',editor: new Ext.form.TextField({}), filter: 'string',
+            renderer: Ext.util.Format.htmlEncode});
         gridColumns.push({text: t("definition"), width: 300, sortable: true, hidden: true, dataIndex: 'definition',editor: new Ext.form.TextField({})});
         gridColumns.push({text: t("type"), width: 150, sortable: true, dataIndex: 'type', filter: 'string',
             editor: new Ext.form.ComboBox({
                 triggerAction: 'all',
                 editable: false,
@@ -283,11 +286,11 @@ pimcore.object.classificationstore.propertiespanel = Class.create({
         var data = grid.getStore().getAt(rowIndex);
         var type = data.data.type;
         var definition = data.data.definition;
         if (definition) {
             definition = Ext.util.JSON.decode(definition);
-            definition.name = data.data.name;
+            definition.name = Ext.util.Format.htmlEncode(data.data.name);
         } else {
             definition = {
                 name: data.data.name
             };
         }
diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/storeTree.js b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/storeTree.js
index 384a1081892..f64b6e7ebe8 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/storeTree.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/object/classificationstore/storeTree.js
@@ -164,12 +164,12 @@ pimcore.object.classificationstore.storeTree = Class.create({
             iconCls: "pimcore_icon_custom_views",
             listeners: {
                 "click": function() {
                     var data = {
                         id: record.data.id,
-                        name: record.data.text,
-                        description: record.data.description
+                        name: Ext.util.Format.htmlDecode(record.data.text),
+                        description: Ext.util.Format.htmlDecode(record.data.description)
                     }
                     var panel = new pimcore.object.classificationstore.storeConfiguration(data, this.applyConfig.bind(this));
                     panel.show();
                 }.bind(this)
             }

Vulnerable code:                 'text' => $item->getName(),
                'description' => $item->getDescription(),
            $resultItem['qtip'] = $item->getDescription() ? $item->getDescription() : ' ';
    return t(value);
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'groupName', filter: 'string'});
        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'groupDescription', filter: 'string'});
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string'});
        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string'});
                        var collectionName = record.data.name;
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'keyName', filter: 'string'});
        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'keyDescription', filter: 'string'});
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string'});
        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string'});
                        var groupName = record.data.name;
                editor: new Ext.form.TextField({})
        gridColumns.push({text: t("title"), width: 200, sortable: false, dataIndex: 'title',editor: new Ext.form.TextField({}), filter: 'string'});
        gridColumns.push({text: t("description"), width: 300, sortable: true, dataIndex: 'description',editor: new Ext.form.TextField({}), filter: 'string'});
            definition.name = data.data.name;
                        name: record.data.text,
                        description: record.data.description
Fixed code:                 'text' => htmlspecialchars($item->getName(), ENT_QUOTES),
                'description' => htmlspecialchars($item->getDescription(), ENT_QUOTES),
            $resultItem['qtip'] = $item->getDescription() ? htmlspecialchars($item->getDescription(), ENT_QUOTES) : ' ';
    return Ext.util.Format.htmlEncode(t(value));
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'groupName', filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'groupDescription', filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
                        var collectionName = Ext.util.Format.htmlEncode(record.data.name);
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'keyName', filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("description"), flex: 200, sortable: true, dataIndex: 'keyDescription', filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("name"), flex: 200, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({}), filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("description"), flex: 300, sortable: true, dataIndex: 'description', editor: new Ext.form.TextField({}), filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
                        var groupName = Ext.util.Format.htmlEncode(record.data.name);
                editor: new Ext.form.TextField({}),
                renderer: Ext.util.Format.htmlEncode
        gridColumns.push({text: t("title"), width: 200, sortable: false, dataIndex: 'title',editor: new Ext.form.TextField({}), filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
        gridColumns.push({text: t("description"), width: 300, sortable: true, dataIndex: 'description',editor: new Ext.form.TextField({}), filter: 'string',
            renderer: Ext.util.Format.htmlEncode});
            definition.name = Ext.util.Format.htmlEncode(data.data.name);
                        name: Ext.util.Format.htmlDecode(record.data.text),
                        description: Ext.util.Format.htmlDecode(record.data.description)