CVE-2023-2615: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Reflected in GitHub repository pimcore/pimcore prior to 10.5.21.
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: 5.4
Published: 2023-05-10T06:15Z
Repository: https://github.com/pimcore/pimcore
Language: PHP
Files changed: bundles/AdminBundle/Resources/public/js/pimcore/settings/properties/predefined.js, models/Property/Predefined.php
Commit date: 2023-04-19 14:14:18 +0200
Commit message: [Security] Fix xss in predefined properties panel (#14943)

* fixed xss in predefined properties panel

* used global helper function for htmlEncode `pimcore.helpers.htmlEncodeTextField`

* added `SecurityHelper::convertHtmlSpecialChars`

* Update predefined.js

prevented xss
Diff: diff --git a/bundles/AdminBundle/Resources/public/js/pimcore/settings/properties/predefined.js b/bundles/AdminBundle/Resources/public/js/pimcore/settings/properties/predefined.js
index 2f91cd275470..2fa2cd8530c4 100644
--- a/bundles/AdminBundle/Resources/public/js/pimcore/settings/properties/predefined.js
+++ b/bundles/AdminBundle/Resources/public/js/pimcore/settings/properties/predefined.js
@@ -55,12 +55,12 @@ pimcore.settings.properties.predefined = Class.create({
 
         var url = Routing.generate('pimcore_admin_settings_properties');
 
         this.store = pimcore.helpers.grid.buildDefaultStore(
             url,
-            ['id',
-
+            [
+                'id',
                 {name: 'name', allowBlank: false},'description',
                 {name: 'key', allowBlank: false},
                 {name: 'type', allowBlank: false}, 'data', 'config',
                 {name: 'ctype', allowBlank: false}, 'inheritable', 'creationDate', 'modificationDate'
 
@@ -106,29 +106,29 @@ pimcore.settings.properties.predefined = Class.create({
             autoLoad: true
         });
 
 
         var propertiesColumns = [
-            {text: t("name"), flex: 100, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({})},
-            {text: t("description"), sortable: true, dataIndex: 'description', editor: new Ext.form.TextArea({}),
+            {text: t("name"), flex: 100, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
+            {text: t("description"), sortable: true, dataIndex: 'description', editor: new Ext.form.TextArea({listeners: {'change': pimcore.helpers.htmlEncodeTextField}}),
                 renderer: function (value, metaData, record, rowIndex, colIndex, store) {
                     if(empty(value)) {
                         return "";
                     }
                     return nl2br(Ext.util.Format.htmlEncode(value));
                }
             },
-            {text: t("key"), flex: 50, sortable: true, dataIndex: 'key', editor: new Ext.form.TextField({})},
+            {text: t("key"), flex: 50, sortable: true, dataIndex: 'key', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
             {text: t("type"), flex: 50, sortable: true, dataIndex: 'type',
                 editor: new Ext.form.ComboBox({
                     triggerAction: 'all',
                     editable: false,
                     store: ["text","document","asset","object","bool","select"]
 
             })},
-            {text: t("value"), flex: 50, sortable: true, dataIndex: 'data', editor: new Ext.form.TextField({})},
-            {text: t("configuration"), flex: 50, sortable: false, dataIndex: 'config', editor: new Ext.form.TextField({})},
+            {text: t("value"), flex: 50, sortable: true, dataIndex: 'data', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
+            {text: t("configuration"), flex: 50, sortable: false, dataIndex: 'config', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
             {text: t("content_type"), flex: 50, sortable: true, dataIndex: 'ctype',
                 editor: new Ext.ux.form.MultiSelect({
                     store: new Ext.data.ArrayStore({
                         fields: ['key', {
                             name: 'value',
@@ -163,15 +163,19 @@ pimcore.settings.properties.predefined = Class.create({
                       return klass;
                     },
                     tooltip: t('delete'),
                     handler: function (grid, rowIndex) {
                         let data = grid.getStore().getAt(rowIndex);
-                        pimcore.helpers.deleteConfirm(t('predefined_properties'),
-                            Ext.util.Format.htmlEncode(data.data.name),
+                        const decodedName = Ext.util.Format.htmlDecode(data.data.name);
+                        
+                        pimcore.helpers.deleteConfirm(
+                            t('predefined_properties'),
+                            Ext.util.Format.htmlEncode(decodedName),
                             function () {
-                            grid.getStore().removeAt(rowIndex);
-                        }.bind(this));
+                                grid.getStore().removeAt(rowIndex);
+                            }.bind(this)
+                        );
                     }.bind(this)
                 }]
             },{
                 xtype: 'actioncolumn',
                 menuText: t('translate'),
diff --git a/models/Property/Predefined.php b/models/Property/Predefined.php
index 99dd0d9405d1..28ed45229bd4 100644
--- a/models/Property/Predefined.php
+++ b/models/Property/Predefined.php
@@ -14,10 +14,11 @@
  */
 
 namespace Pimcore\Model\Property;
 
 use Pimcore\Model;
+use Pimcore\Security\SecurityHelper;
 
 /**
  * @internal
  *
  * @method bool isWriteable()
@@ -175,11 +176,11 @@ final class Predefined extends Model\AbstractModel
      *
      * @return $this
      */
     public function setKey($key)
     {
-        $this->key = $key;
+        $this->key = SecurityHelper::convertHtmlSpecialChars($key);
 
         return $this;
     }
 
     /**
@@ -187,11 +188,11 @@ final class Predefined extends Model\AbstractModel
      *
      * @return $this
      */
     public function setName($name)
     {
-        $this->name = $name;
+        $this->name = SecurityHelper::convertHtmlSpecialChars($name);
 
         return $this;
     }
 
     /**
@@ -211,11 +212,11 @@ final class Predefined extends Model\AbstractModel
      *
      * @return $this
      */
     public function setData($data)
     {
-        $this->data = $data;
+        $this->data = SecurityHelper::convertHtmlSpecialChars($data);
 
         return $this;
     }
 
     /**
@@ -251,11 +252,11 @@ final class Predefined extends Model\AbstractModel
      *
      * @return $this
      */
     public function setConfig($config)
     {
-        $this->config = $config;
+        $this->config = SecurityHelper::convertHtmlSpecialChars($config);
 
         return $this;
     }
 
     /**
@@ -303,11 +304,11 @@ final class Predefined extends Model\AbstractModel
      *
      * @return $this
      */
     public function setDescription($description)
     {
-        $this->description = $description;
+        $this->description = SecurityHelper::convertHtmlSpecialChars($description);
 
         return $this;
     }
 
     /**

Vulnerable code:             ['id',

            {text: t("name"), flex: 100, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({})},
            {text: t("description"), sortable: true, dataIndex: 'description', editor: new Ext.form.TextArea({}),
            {text: t("key"), flex: 50, sortable: true, dataIndex: 'key', editor: new Ext.form.TextField({})},
            {text: t("value"), flex: 50, sortable: true, dataIndex: 'data', editor: new Ext.form.TextField({})},
            {text: t("configuration"), flex: 50, sortable: false, dataIndex: 'config', editor: new Ext.form.TextField({})},
                        pimcore.helpers.deleteConfirm(t('predefined_properties'),
                            Ext.util.Format.htmlEncode(data.data.name),
                            grid.getStore().removeAt(rowIndex);
                        }.bind(this));
        $this->key = $key;
        $this->name = $name;
        $this->data = $data;
        $this->config = $config;
        $this->description = $description;
Fixed code:             [
                'id',
            {text: t("name"), flex: 100, sortable: true, dataIndex: 'name', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
            {text: t("description"), sortable: true, dataIndex: 'description', editor: new Ext.form.TextArea({listeners: {'change': pimcore.helpers.htmlEncodeTextField}}),
            {text: t("key"), flex: 50, sortable: true, dataIndex: 'key', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
            {text: t("value"), flex: 50, sortable: true, dataIndex: 'data', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
            {text: t("configuration"), flex: 50, sortable: false, dataIndex: 'config', editor: new Ext.form.TextField({listeners: {'change': pimcore.helpers.htmlEncodeTextField}})},
                        const decodedName = Ext.util.Format.htmlDecode(data.data.name);
                        
                        pimcore.helpers.deleteConfirm(
                            t('predefined_properties'),
                            Ext.util.Format.htmlEncode(decodedName),
                                grid.getStore().removeAt(rowIndex);
                            }.bind(this)
                        );
use Pimcore\Security\SecurityHelper;
        $this->key = SecurityHelper::convertHtmlSpecialChars($key);
        $this->name = SecurityHelper::convertHtmlSpecialChars($name);
        $this->data = SecurityHelper::convertHtmlSpecialChars($data);
        $this->config = SecurityHelper::convertHtmlSpecialChars($config);
        $this->description = SecurityHelper::convertHtmlSpecialChars($description);