CVE-2022-25321: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: An issue was discovered in Cerebrate through 1.4. XSS could occur in the bookmarks component.
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: 6.1
Published: 2022-02-18T06:15Z
Repository: https://github.com/cerebrate-project/cerebrate
Language: PHP
Files changed: templates/element/Settings/field.php
Commit date: 2022-02-07 11:43:09 +0100
Commit message: fix: [settings:settingField] Enforce sanitization of input fields

- As reported by Dawid Czarnecki from Zigrin Security

Diff: diff --git a/templates/element/Settings/field.php b/templates/element/Settings/field.php
index 34bdde8d..d52bc146 100644
--- a/templates/element/Settings/field.php
+++ b/templates/element/Settings/field.php
@@ -11,32 +11,32 @@
                         (!empty($setting['error']) ? 'is-invalid' : ''),
                         (!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
                         (!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
                     ],
                     ($setting['type'] == 'textarea' ? '' : 'type') => ($setting['type'] == 'textarea' ? '' : 'text'),
-                    'id' => $settingId,
-                    'data-setting-name' => $settingName,
-                    'value' => isset($setting['value']) ? $setting['value'] : "",
-                    'placeholder' => $setting['default'] ?? '',
-                    'aria-describedby' => "{$settingId}Help"
+                    'id' => h($settingId),
+                    'data-setting-name' => h($settingName),
+                    'value' => isset($setting['value']) ? h($setting['value']) : "",
+                    'placeholder' => empty($setting['default']) ? '' : h($setting['default']),
+                    'aria-describedby' => h("{$settingId}Help")
                 ]
             );
         })($settingName, $setting, $this);
 
     } elseif ($setting['type'] == 'boolean') {
         $input = (function ($settingName, $setting, $appView) {
             $settingId = str_replace('.', '_', $settingName);
             return $this->Bootstrap->switch([
                 'label' => h($setting['description']),
                 'checked' => !empty($setting['value']),
-                'id' => $settingId,
+                'id' => h($settingId),
                 'class' => [
                     (!empty($setting['error']) ? 'is-invalid' : ''),
                     (!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
                 ],
                 'attrs' => [
-                    'data-setting-name' => $settingName
+                    'data-setting-name' => h($settingName)
                 ]
             ]);
         })($settingName, $setting, $this);
         $description = '';
 
@@ -51,20 +51,20 @@
                     (!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
                 ],
                 'type' => 'number',
                 'min' => '0',
                 'step' => 1,
-                'id' => $settingId,
-                'data-setting-name' => $settingName,
-                'aria-describedby' => "{$settingId}Help"
+                'id' => h($settingId),
+                'data-setting-name' => h($settingName),
+                'aria-describedby' => h("{$settingId}Help")
             ]);
         })($settingName, $setting, $this);
 
     } elseif ($setting['type'] == 'select' || $setting['type'] == 'multi-select') {
         $input = (function ($settingName, $setting, $appView) {
             $settingId = str_replace('.', '_', $settingName);
-            $setting['value'] = $setting['value'] ?? '';
+            $setting['value'] = empty($setting['value']) ? '' : h($setting['value']);
             if ($setting['type'] == 'multi-select') {
                 if (!is_array($setting['value'])) {
                     $firstChar = substr($setting['value'], 0, 1);
                     if ($firstChar != '{' && $firstChar != '[') { // make sure to cast a simple string into an array
                         $setting['value'] = sprintf('["%s"]', $setting['value']);
@@ -75,11 +75,11 @@
             $options = [];
             $options[] = $appView->Bootstrap->genNode('option', ['value' => '-1', 'data-is-empty-option' => '1'], __('Select an option'));
             foreach ($setting['options'] as $key => $value) {
                 $optionParam = [
                     'class' => [],
-                    'value' => $key,
+                    'value' => h($key),
                 ];
                 if ($setting['type'] == 'multi-select') {
                     if (in_array($key, $setting['value'])) {
                         $optionParam['selected'] = 'selected';
                     }
@@ -98,13 +98,13 @@
                     (!empty($setting['error']) ? 'is-invalid' : ''),
                     (!empty($setting['error']) ? "border-{$appView->get('variantFromSeverity')[$setting['severity']]}" : ''),
                     (!empty($setting['error']) ? $appView->get('variantFromSeverity')[$setting['severity']] : ''),
                 ],
                 ($setting['type'] == 'multi-select' ? 'multiple' : '') => ($setting['type'] == 'multi-select' ? 'multiple' : ''),
-                'id' => $settingId,
-                'data-setting-name' => $settingName,
-                'placeholder' => $setting['default'] ?? '',
-                'aria-describedby' => "{$settingId}Help"
+                'id' => h($settingId),
+                'data-setting-name' => h($settingName),
+                'placeholder' => empty($setting['default']) ? '' : h($setting['default']),
+                'aria-describedby' => h("{$settingId}Help")
             ], $options);
         })($settingName, $setting, $this);
     }
     echo $input;

Vulnerable code:                     'id' => $settingId,
                    'data-setting-name' => $settingName,
                    'value' => isset($setting['value']) ? $setting['value'] : "",
                    'placeholder' => $setting['default'] ?? '',
                    'aria-describedby' => "{$settingId}Help"
                'id' => $settingId,
                    'data-setting-name' => $settingName
                'id' => $settingId,
                'data-setting-name' => $settingName,
                'aria-describedby' => "{$settingId}Help"
            $setting['value'] = $setting['value'] ?? '';
                    'value' => $key,
                'id' => $settingId,
                'data-setting-name' => $settingName,
                'placeholder' => $setting['default'] ?? '',
                'aria-describedby' => "{$settingId}Help"
Fixed code:                     'id' => h($settingId),
                    'data-setting-name' => h($settingName),
                    'value' => isset($setting['value']) ? h($setting['value']) : "",
                    'placeholder' => empty($setting['default']) ? '' : h($setting['default']),
                    'aria-describedby' => h("{$settingId}Help")
                'id' => h($settingId),
                    'data-setting-name' => h($settingName)
                'id' => h($settingId),
                'data-setting-name' => h($settingName),
                'aria-describedby' => h("{$settingId}Help")
            $setting['value'] = empty($setting['value']) ? '' : h($setting['value']);
                    'value' => h($key),
                'id' => h($settingId),
                'data-setting-name' => h($settingName),
                'placeholder' => empty($setting['default']) ? '' : h($setting['default']),
                'aria-describedby' => h("{$settingId}Help")