CVE-2023-44796: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross Site Scripting (XSS) vulnerability in LimeSurvey before version 6.2.9-230925 allows a remote attacker to escalate privileges via a crafted script to the _generaloptions_panel.php 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: 5.4
Published: 2023-11-18T00:15Z
Repository: https://github.com/limesurvey/limesurvey
Language: PHP
Files changed: application/models/TemplateConfiguration.php, application/views/admin/globalsettings/_general.php, application/views/admin/survey/subview/accordion/_generaloptions_panel.php
Commit date: 2023-09-22 17:35:41 +0200
Commit message: Fixed issue #19099: [security] XSS vulnerability caused by themeOptions/importManifest (#3483)

Co-authored-by: Denis Chenu <shnoulle@localhost.localdomain>
Diff: diff --git a/application/models/TemplateConfiguration.php b/application/models/TemplateConfiguration.php
index dfb2dc9a8a..38f86dac19 100755
--- a/application/models/TemplateConfiguration.php
+++ b/application/models/TemplateConfiguration.php
@@ -117,10 +117,13 @@ class TemplateConfiguration extends TemplateConfig
         // NOTE: you should only define rules for those attributes that
         // will receive user inputs.
         return array(
             array('template_name', 'required'),
             array('id, sid, gsid', 'numerical', 'integerOnly' => true),
+            array('template_name', 'filter', 'filter' => function ($value) {
+                return sanitize_filename($value, false, false, false);
+            }),
             array('template_name', 'length', 'max' => 150),
             array('cssframework_name', 'length', 'max' => 45),
             array('files_css, files_js, files_print_css, options, cssframework_css, cssframework_js, packages_to_load',
                 'safe'),
             array('options', 'sanitizeImagePathsOnJson'),
@@ -722,11 +725,12 @@ class TemplateConfiguration extends TemplateConfig
             }
             if (!$oSurveysGroups->hasPermission('surveys', 'update')) {
                 return '';
             }
         }
-        $templateName = CHtml::encode($this->template_name);
+        /* Use sanitized filename for previous bad upload */
+        $templateName = sanitize_filename($this->template_name, false, false, false);
         $sEditorUrl = App()->getController()->createUrl(
             'admin/themes/sa/view',
             array("templatename" => $templateName)
         );
         $sExtendUrl = App()->getController()->createUrl('admin/themes/sa/templatecopy');
@@ -745,31 +749,31 @@ class TemplateConfiguration extends TemplateConfig
 
         $dropdownItems = [];
         $dropdownItems[] = [
             'title'            => gT('Theme editor'),
             'url'              => $sEditorUrl,
-            'linkId'           => 'template_editor_link_' . $templateName,
+            'linkId'           => 'template_editor_link_' . $this->id,
             'linkClass'        => '',
             'iconClass'        => 'ri-brush-fill',
             'enabledCondition' => App()->getController()->action->id !== "surveysgroups",
 
         ];
 
         $dropdownItems[] = [
             'title'            => gT('Theme options'),
             'url'              => $sOptionUrl,
-            'linkId'           => 'template_options_link_' . $templateName ,
+            'linkId'           => 'template_options_link_' . $this->id ,
             'linkClass'        => '',
             'iconClass'        => 'ri-dashboard-3-fill',
             'enabledCondition' => $this->getHasOptionPage(),
         ];
 
 
         $dropdownItems[] = [
             'title'            => gT('Extend'),
             'url'              => $sExtendUrl,
-            'linkId'           => 'extendthis_' . $templateName,
+            'linkId'           => 'extendthis_' . $this->id,
             'linkClass'        => 'selector--ConfirmModal ',
             'iconClass'        => 'ri-file-copy-line text-success',
             'enabledCondition' => App()->getController()->action->id !== "surveysgroups",
             'linkAttributes'   => [
                 'title'            => sprintf(gT('Type in the new name to extend %s'), $templateName),
@@ -788,11 +792,11 @@ class TemplateConfiguration extends TemplateConfig
 
 
         $dropdownItems[] = [
             'title'            => gT('Uninstall'),
             'url'              => $sUninstallUrl,
-            'linkId'           => 'remove_fromdb_link_' . $templateName,
+            'linkId'           => 'remove_fromdb_link_' . $this->id,
             'linkClass'        => 'selector--ConfirmModal ',
             'iconClass'        => 'ri-delete-bin-fill text-danger',
             'enabledCondition' => App()->getController()->action->id !== "surveysgroups" &&
                                     $templateName != App()->getConfig('defaulttheme'),
             'linkAttributes'   => [
@@ -807,11 +811,11 @@ class TemplateConfiguration extends TemplateConfig
         ];
 
         $dropdownItems[] = [
             'title'            => gT('Reset'),
             'url'              => $sResetUrl,
-            'linkId'           => 'remove_fromdb_link_' . $templateName,
+            'linkId'           => 'remove_fromdb_link_' . $this->id,
             'linkClass'        => 'selector--ConfirmModal ',
             'iconClass'        => 'ri-refresh-line text-warning',
             'enabledCondition' => App()->getController()->action->id !== "surveysgroups",
             'linkAttributes'   => [
                 'title'            => gT('Reset this theme'),
diff --git a/application/views/admin/globalsettings/_general.php b/application/views/admin/globalsettings/_general.php
index f8cc5dc24e..97931c0662 100644
--- a/application/views/admin/globalsettings/_general.php
+++ b/application/views/admin/globalsettings/_general.php
@@ -40,12 +40,12 @@ $dateformatdata                  = getDateFormatData(Yii::app()->session['datefo
                 echo((Yii::app()->getConfig("demoMode") == true) ? '*' : ''); ?>
             </label>
             <div class="col-12">
                 <select class="form-select" name="defaulttheme" id="defaulttheme">
                     <?php foreach ($templatenames as $templatename) : ?>
-                        <option value='<?php echo $templatename; ?>' <?php echo ($thisdefaulttheme == $templatename) ? "selected='selected'" : "" ?> >
-                            <?php echo $templatename; ?>
+                        <option value='<?php echo CHtml::encode($themeName); ?>' <?php echo ($thisdefaulttheme == $templatename) ? "selected='selected'" : "" ?> >
+                            <?php echo CHtml::encode($templatename); ?>
                         </option>
                     <?php endforeach; ?>
                 </select>
             </div>
         </div>
@@ -73,12 +73,12 @@ $dateformatdata                  = getDateFormatData(Yii::app()->session['datefo
                 <?php eT("Administration theme:"); ?>
             </label>
             <div class="col-12">
                 <select class="form-select" name="admintheme" id="admintheme">
                     <?php foreach ($aListOfThemeObjects as $templatename => $templateconfig): ?>
-                        <option value='<?php echo $templatename; ?>' <?php echo ($thisadmintheme == $templatename) ? "selected='selected'" : "" ?> >
-                            <?php echo $templateconfig->metadata->name; ?>
+                        <option value='<?php echo CHtml::encode($templatename); ?>' <?php echo ($thisadmintheme == $templatename) ? "selected='selected'" : "" ?> >
+                            <?php echo CHtml::encode($templateconfig->metadata->name); ?>
                         </option>
                     <?php endforeach; ?>
                 </select>
             </div>
             <?php if (Permission::model()->hasGlobalPermission('superadmin', 'read')): ?>
diff --git a/application/views/admin/survey/subview/accordion/_generaloptions_panel.php b/application/views/admin/survey/subview/accordion/_generaloptions_panel.php
index 49b567c9da..37880de0d9 100755
--- a/application/views/admin/survey/subview/accordion/_generaloptions_panel.php
+++ b/application/views/admin/survey/subview/accordion/_generaloptions_panel.php
@@ -322,25 +322,25 @@ Yii::app()->getClientScript()->registerScript("GeneralOption-confirm-language",
                 <?php $themeConf = TemplateConfiguration::getInstanceFromTemplateName(($oSurvey->template === 'inherit') ? $oSurveyOptions->template : $oSurvey->template) ?>
                 <select id='template' style="width:100%;" class="form-select activate-search" name='template' data-updateurl='<?php echo App()->createUrl('themeOptions/getPreviewTag') ?>'
                         data-inherit-template-name='<?= $themeConf->template_name ?>'>
                     <?php if ($bShowInherited) : ?>
                         <option value="inherit" <?= ($oSurvey->template == 'inherit') ? 'selected="selected"' : ''; ?>>
-                            <?= gT('Inherit') . ' [' . $themeConf->template_name . ']'; ?>
+                            <?= gT('Inherit') . ' [' . CHtml::encode($themeConf->template_name) . ']'; ?>
                         </option>
                     <?php endif; ?>
                     <?php
                     $aTemplateList = Template::getTemplateListWithPreviews();
                     foreach ($aTemplateList as $templateName => $preview) {
                         if (Permission::model()->hasGlobalPermission('templates', 'read') || Permission::model()->hasTemplatePermission($templateName
                             ) || $oSurvey->template == htmlspecialchars((string) $templateName)) { ?>
-                            <option value='<?php echo $templateName; ?>'
+                            <option value='<?php echo CHtml::encode($templateName); ?>'
                                 <?php if ($oSurvey->template && htmlspecialchars((string) $templateName) === $themeConf->template_name && $oSurvey->template !== 'inherit') { ?>
                                     selected='selected'
                                 <?php } elseif (!$oSurvey->template && $templateName === App()->getConfig('defaulttheme') && $oSurvey->template !== 'inherit') { ?>
                                     selected='selected'
                                 <?php } ?>
-                            ><?php echo $templateName; ?></option>
+                            ><?php echo CHtml::encode($templateName); ?></option>
                         <?php } ?>
 
                     <?php } ?>
                 </select>
             </div>

Vulnerable code:         $templateName = CHtml::encode($this->template_name);
            'linkId'           => 'template_editor_link_' . $templateName,
            'linkId'           => 'template_options_link_' . $templateName ,
            'linkId'           => 'extendthis_' . $templateName,
            'linkId'           => 'remove_fromdb_link_' . $templateName,
            'linkId'           => 'remove_fromdb_link_' . $templateName,
                        <option value='<?php echo $templatename; ?>' <?php echo ($thisdefaulttheme == $templatename) ? "selected='selected'" : "" ?> >
                            <?php echo $templatename; ?>
                        <option value='<?php echo $templatename; ?>' <?php echo ($thisadmintheme == $templatename) ? "selected='selected'" : "" ?> >
                            <?php echo $templateconfig->metadata->name; ?>
                            <?= gT('Inherit') . ' [' . $themeConf->template_name . ']'; ?>
                            <option value='<?php echo $templateName; ?>'
                            ><?php echo $templateName; ?></option>
Fixed code:             array('template_name', 'filter', 'filter' => function ($value) {
                return sanitize_filename($value, false, false, false);
            }),
        /* Use sanitized filename for previous bad upload */
        $templateName = sanitize_filename($this->template_name, false, false, false);
            'linkId'           => 'template_editor_link_' . $this->id,
            'linkId'           => 'template_options_link_' . $this->id ,
            'linkId'           => 'extendthis_' . $this->id,
            'linkId'           => 'remove_fromdb_link_' . $this->id,
            'linkId'           => 'remove_fromdb_link_' . $this->id,
                        <option value='<?php echo CHtml::encode($themeName); ?>' <?php echo ($thisdefaulttheme == $templatename) ? "selected='selected'" : "" ?> >
                            <?php echo CHtml::encode($templatename); ?>
                        <option value='<?php echo CHtml::encode($templatename); ?>' <?php echo ($thisadmintheme == $templatename) ? "selected='selected'" : "" ?> >
                            <?php echo CHtml::encode($templateconfig->metadata->name); ?>
                            <?= gT('Inherit') . ' [' . CHtml::encode($themeConf->template_name) . ']'; ?>
                            <option value='<?php echo CHtml::encode($templateName); ?>'
                            ><?php echo CHtml::encode($templateName); ?></option>