[SECURITY] Prevent possible XSS in install tool 36/59536/2
authorFrank Naegler <frank.naegler@typo3.org>
Tue, 22 Jan 2019 08:43:00 +0000 (09:43 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 22 Jan 2019 08:43:03 +0000 (09:43 +0100)
Resolves: #86455
Releases: master, 9.5
Security-Commit: 161663d336f7a6c52a87359a1d1ac01037e5c768
Security-Bulletin: TYPO3-CORE-SA-2019-004
Change-Id: Ief21fcf68f14cb756f140b4c709ddb51f447e544
Reviewed-on: https://review.typo3.org/59536
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/install/Resources/Public/JavaScript/Modules/ExtensionConfiguration.js
typo3/sysext/install/Resources/Public/JavaScript/Modules/LanguagePacks.js
typo3/sysext/install/Resources/Public/JavaScript/Modules/SystemMaintainer.js
typo3/sysext/install/Resources/Public/JavaScript/Modules/UpgradeDocs.js
typo3/sysext/install/Resources/Public/JavaScript/Modules/UpgradeWizards.js

index 37f5f93..96905a7 100644 (file)
@@ -150,39 +150,45 @@ define([
      */
     initializeWrap: function() {
       this.currentModal.find('.t3js-emconf-offset').each(function() {
-        var $me = $(this),
-          $parent = $me.parent(),
-          id = $me.attr('id'),
-          val = $me.attr('value'),
-          valArr = val.split(',');
-
-        $me.attr('data-offsetfield-x', '#' + id + '_offset_x')
+        var $me = $(this);
+        var $parent = $me.parent();
+        var id = $me.attr('id');
+        var val = $me.attr('value');
+        var valArr = val.split(',');
+
+        $me
+          .attr('data-offsetfield-x', '#' + id + '_offset_x')
           .attr('data-offsetfield-y', '#' + id + '_offset_y')
           .wrap('<div class="hidden"></div>');
 
-        var elementX = '' +
-          '<div class="form-multigroup-item">' +
-          '<div class="input-group">' +
-          '<div class="input-group-addon">x</div>' +
-          '<input id="' + id + '_offset_x" class="form-control t3js-emconf-offsetfield" data-target="#' + id + '" value="' + $.trim(valArr[0]) + '">' +
-          '</div>' +
-          '</div>';
-        var elementY = '' +
-          '<div class="form-multigroup-item">' +
-          '<div class="input-group">' +
-          '<div class="input-group-addon">y</div>' +
-          '<input id="' + id + '_offset_y" class="form-control t3js-emconf-offsetfield" data-target="#' + id + '" value="' + $.trim(valArr[1]) + '">' +
-          '</div>' +
-          '</div>';
-
-        var offsetGroup = '<div class="form-multigroup-wrap">' + elementX + elementY + '</div>';
+        var elementX = $('<div>', {'class': 'form-multigroup-item'}).append(
+            $('<div>', {'class': 'input-group'}).append(
+              $('<div>', {'class': 'input-group-addon'}).text('x'),
+              $('<input>', {
+                'id': id + '_offset_x',
+                'class': 'form-control t3js-emconf-offsetfield',
+                'data-target': '#' + id,
+                'value': $.trim(valArr[0])
+              })
+            )
+        );
+        var elementY = $('<div>', {'class': 'form-multigroup-item'}).append(
+            $('<div>', {'class': 'input-group'}).append(
+              $('<div>', {'class': 'input-group-addon'}).text('y'),
+              $('<input>', {
+                'id': id + '_offset_y',
+                'class': 'form-control t3js-emconf-offsetfield',
+                'data-target': '#' + id,
+                'value': $.trim(valArr[1])
+              })
+            )
+        );
+
+        var offsetGroup = $('<div>', {'class': 'form-multigroup-wrap'}).append(elementX, elementY);
         $parent.append(offsetGroup);
-        $parent.find('.t3js-emconf-offset').keyup(function() {
-          var $target = $($(this).data('target'));
-          $target.attr(
-            'value',
-            $($target.data('offsetfield-x')).val() + ',' + $($target.data('offsetfield-y')).val()
-          );
+        $parent.find('.t3js-emconf-offsetfield').keyup(function() {
+          var $target = $parent.find($(this).data('target'));
+          $target.val($parent.find($target.data('offsetfield-x')).val() + ',' + $parent.find($target.data('offsetfield-y')).val());
         });
       });
 
@@ -197,23 +203,28 @@ define([
           .attr('data-wrapfield-end', '#' + id + '_wrap_end')
           .wrap('<div class="hidden"></div>');
 
-        var elementStart = '' +
-          '<div class="form-multigroup-item">' +
-          '<input id="' + id + '_wrap_start" class="form-control t3js-emconf-wrapfield" data-target="#' + id + '" value="' + $.trim(valArr[0]) + '">' +
-          '</div>';
-        var elementEnd = '' +
-          '<div class="form-multigroup-item">' +
-          '<input id="' + id + '_wrap_end" class="form-control t3js-emconf-wrapfield" data-target="#' + id + '" value="' + $.trim(valArr[1]) + '">' +
-          '</div>';
-
-        var wrapGroup = '<div class="form-multigroup-wrap">' + elementStart + elementEnd + '</div>';
+        var wrapGroup = $('<div>', {'class': 'form-multigroup-wrap'}).append(
+          $('<div>', {'class': 'form-multigroup-item'}).append(
+            $('<input>', {
+              'id': id + '_wrap_start',
+              'class': 'form-control t3js-emconf-wrapfield',
+              'data-target': '#' + id,
+              'value': $.trim(valArr[0])
+            })
+          ),
+          $('<div>', {'class': 'form-multigroup-item'}).append(
+            $('<input>', {
+              'id': id + '_wrap_end',
+              'class': 'form-control t3js-emconf-wrapfield',
+              'data-target': '#' + id,
+              'value': $.trim(valArr[1])
+            })
+          )
+        );
         $parent.append(wrapGroup);
         $parent.find('.t3js-emconf-wrapfield').keyup(function() {
-          var $target = $($(this).data('target'));
-          $target.attr(
-            'value',
-            $($target.data('wrapfield-start')).val() + '|' + $($target.data('wrapfield-end')).val()
-          );
+          var $target = $parent.find($(this).data('target'));
+          $target.val($parent.find($target.data('wrapfield-start')).val() + '|' + $parent.find($target.data('wrapfield-end')).val());
         });
       });
     }
index dedc5ab..744bef7 100644 (file)
@@ -217,13 +217,19 @@ define([
         failed: 0
       };
 
-      var message = '<div class="progress">' +
-        '<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0;">' +
-          '<span class="text-nowrap">0 of ' + this.packsUpdateDetails.toHandle + ' language packs updated</span>' +
-        '</div>' +
-      '</div>';
-
-      $outputContainer.empty().append(message);
+      $outputContainer.empty().append(
+        $('<div>', {'class': 'progress'}).append(
+          $('<div>', {
+            'class': 'progress-bar progress-bar-info',
+            'role': 'progressbar',
+            'aria-valuenow': 0,
+            'aria-valuemin': 0,
+            'aria-valuemax': 100,
+            'style': 'width: 0;'
+          }).append(
+            $('<span>', {'class': 'text-nowrap'}).text('0 of ' + this.packsUpdateDetails.toHandle + ' language packs updated')
+          )
+      ));
       $contentContainer.empty();
 
       isos.forEach(function(iso) {
@@ -328,54 +334,78 @@ define([
       var activateIcon = this.currentModal.find(this.selectorActivateLanguageIcon).html();
       var deactivateIcon = this.currentModal.find(this.selectorDeactivateLanguageIcon).html();
       var updateIcon = this.currentModal.find(this.selectorLanguageUpdateIcon).html();
-      var html = '<h3>Active languages</h3>' +
-        '<table class="table table-striped table-bordered">' +
-        '<thead><tr>' +
-          '<th>' +
-            '<button class="btn btn-default t3js-languagePacks-addLanguage-toggle" type="button">' +
-              '<span> ' + activateIcon + '</span>Add language' +
-            '</button> ' +
-            '<button class="btn btn-default t3js-languagePacks-update" type="button">' +
-              '<span> ' + updateIcon + '</span>Update all' +
-            '</button>' +
-          '</th>' +
-          '<th>Language</th>' +
-          '<th>Locale</th>' +
-          '<th>Dependencies</th>' +
-          '<th>Last update</th>' +
-        '</tr></thead>' +
-        '<tbody>';
+      var $markupContainer = $('<div>');
+
+      var $tbody = $('<tbody>');
       data.languages.forEach(function(language) {
         var active = language.active;
+        var $tr = $('<tr>');
         if (active) {
-          html = html +
-            '<tr>' +
-              '<td>' +
-                '<a class="btn btn-default t3js-languagePacks-deactivateLanguage" data-iso="' + language.iso + '" data-toggle="tooltip" title="Deactivate">' +
-                  deactivateIcon +
-                '</a> ' +
-                '<a class="btn btn-default t3js-languagePacks-update" data-iso="' + language.iso + '" data-toggle="tooltip" title="Download language packs">' +
-                  updateIcon +
-                '</a>' +
-              '</td>';
+          $tbody.append(
+            $tr.append(
+              $('<td>').append(
+                $('<a>', {
+                  'class': 'btn btn-default t3js-languagePacks-deactivateLanguage',
+                  'data-iso': language.iso,
+                  'data-toggle': 'tooltip',
+                  'title': 'Deactivate'
+                }).append(deactivateIcon),
+                $('<a>', {
+                  'class': 'btn btn-default t3js-languagePacks-update',
+                  'data-iso': language.iso,
+                  'data-toggle': 'tooltip',
+                  'title': 'Download language packs'
+                }).append(updateIcon)
+              )
+            )
+          );
         } else {
-          html = html +
-            '<tr class="t3-languagePacks-inactive t3js-languagePacks-inactive" style="display:none">' +
-              '<td>' +
-                '<a class="btn btn-default t3js-languagePacks-activateLanguage" data-iso="' + language.iso + '" data-toggle="tooltip" title="Activate">' +
-                  activateIcon +
-                '</a>' +
-              '</td>';
+          $tbody.append(
+            $tr.addClass('t3-languagePacks-inactive t3js-languagePacks-inactive').css({'display': 'none'}).append(
+              $('<td>').append(
+                $('<a>', {
+                  'class': 'btn btn-default t3js-languagePacks-activateLanguage',
+                  'data-iso': language.iso,
+                  'data-toggle': 'tooltip',
+                  'title': 'Activate'
+                }).append(activateIcon)
+              )
+            )
+          );
         }
-        html = html +
-          '<td>' + language.name +'</td>' +
-          '<td>' + language.iso +'</td>' +
-          '<td>' + language.dependencies.join(', ') +'</td>' +
-          '<td>' + (language.lastUpdate === null ? '' : language.lastUpdate) +'</td>' +
-          '</tr>';
+        $tr.append(
+          $('<td>').text(language.name),
+          $('<td>').text(language.iso),
+          $('<td>').text(language.dependencies.join(', ')),
+          $('<td>').text(language.lastUpdate === null ? '' : language.lastUpdate),
+        );
+        $tbody.append($tr);
       });
-      html = html + '</tbody></table>';
-      return html;
+      $markupContainer.append(
+        $('<h3>').text('Active languages'),
+        $('<table>', {'class': 'table table-striped table-bordered'}).append(
+          $('<thead>').append(
+            $('<tr>').append(
+              $('<th>').append(
+                $('<button>', {'class': 'btn btn-default t3js-languagePacks-addLanguage-toggle', 'type': 'button'}).append(
+                  $('<span>').append(activateIcon),
+                  ' Add language'
+                ),
+                $('<button>', {'class': 'btn btn-default t3js-languagePacks-update', 'type': 'button'}).append(
+                  $('<span>').append(updateIcon),
+                  ' Update all'
+                )
+              ),
+              $('<th>').text('Language'),
+              $('<th>').text('Locale'),
+              $('<th>').text('Dependencies'),
+              $('<th>').text('Last update'),
+            )
+          ),
+          $tbody
+        )
+      );
+      return $markupContainer.html();
     },
 
     extensionMatrixHtml: function(data) {
@@ -385,18 +415,30 @@ define([
       var extensionTitle = '';
       var allPackagesExist = true;
       var rowCount = 0;
-      var html = '<h3>Translation status</h3>' +
-        '<table class="table table-striped table-bordered"><thead><tr>' +
-        '<th>Extension</th>' +
-        '<th>Key</th>';
+      var $markupContainer = $('<div>');
+
+      var $headerRow = $('<tr>');
+      $headerRow.append(
+        $('<th>').text('Extension'),
+        $('<th>').text('Key'),
+      );
       data.activeLanguages.forEach(function(language) {
-        html = html + '<th>' +
-          '<a class="btn btn-default t3js-languagePacks-update" data-iso="' + language + '" data-toggle="tooltip" title="Download and update all language packs">' +
-            '<span>' + updateIcon + '</span> ' + language +
-          '</a>' +
-        '</th>';
+        $headerRow.append(
+          $('<th>').append(
+            $('<a>', {
+              'class': 'btn btn-default t3js-languagePacks-update',
+              'data-iso': language,
+              'data-toggle': 'tooltip',
+              'title': 'Download and update all language packs'
+            }).append(
+              $('<span>').append(updateIcon),
+              ' ' + language
+            )
+          )
+        )
       });
-      html = html + '</tr></thead><tbody>';
+
+      var $tbody = $('<tbody>');
       data.extensions.forEach(function(extension) {
         allPackagesExist = true;
         extension.packs.forEach(function(pack) {
@@ -409,36 +451,56 @@ define([
         }
         rowCount++;
         if (extension.icon !== '') {
-          extensionTitle = '<img style="max-height: 16px; max-width: 16px;" src="../' + extension.icon + '" alt="' + extension.title + '" /> ' +
-            '<span>' + extension.title + '</span>';
+          extensionTitle = $('<span>').append(
+            $('<img>', {
+              'style': 'max-height: 16px; max-width: 16px;',
+              'src': '../' + extension.icon,
+              'alt': extension.title
+            }),
+            $('<span>').text(extension.title)
+          );
         } else {
-          extensionTitle = extension.title;
+          extensionTitle = $('<span>').text(extension.title)
         }
-        html = html + '<tr>' +
-          '<td>' + extensionTitle + '</td>' +
-          '<td>' + extension.key + '</td>';
+        var $tr = $('<tr>');
+        $tr.append(
+          $('<td>').html(extensionTitle),
+          $('<td>').text(extension.key)
+        );
         extension.packs.forEach(function(pack) {
-          html = html + '<td>';
           if (pack.exists !== true) {
             if (pack.lastUpdate !== null) {
               tooltip = 'No language pack available when tried at ' + pack.lastUpdate + '. Click to re-try.';
             } else {
               tooltip = 'Language pack not downloaded. Click to download';
             }
-            html = html +
-              '<a class="btn btn-default t3js-languagePacks-update" data-extension="' + extension.key + '" data-iso="' + pack.iso + '" data-toggle="tooltip" title="' + tooltip + '">' +
-                packMissesIcon +
-              '</a>';
+            $tr.append(
+              $('<td>').append(
+                $('<a>', {
+                  'class': 'btn btn-default t3js-languagePacks-update',
+                  'data-extension': extension.key,
+                  'data-iso': pack.iso,
+                  'data-toggle': 'tooltip',
+                  'title': tooltip
+                }).append(packMissesIcon)
+              )
+            );
           }
-          html = html + '</td>';
         });
-        html = html + '</tr>';
+        $tbody.append($tr);
       });
-      html = html + '</tbody>';
+
+      $markupContainer.append(
+        $('<h3>').text('Translation status'),
+        $('<table>', {'class': 'table table-striped table-bordered'}).append(
+          $('<thead>').append($headerRow),
+          $tbody
+        )
+      );
       if (rowCount === 0) {
         return InfoBox.render(Severity.ok, 'Language packs have been found for every installed extension.', 'To download the latest changes, use the refresh button in the list above.');
       }
-      return html;
+      return $markupContainer.html();
     },
     getNotificationBox: function() {
       return this.currentModal.find(this.selectorNotifications);
index 3ec5c88..cdeb5d5 100644 (file)
@@ -76,13 +76,11 @@ define([
                 if (element.disable) {
                   name = '[DISABLED] ' + name;
                 }
-                var selected = '';
+                var $option = $('<option>', {'value': element.uid}).text(name);
                 if (element.isSystemMaintainer) {
-                  selected = 'selected="selected"';
+                  $option.attr('selected', 'selected');
                 }
-                modalContent.find(self.selectorChosenField).append(
-                  '<option value="' + element.uid + '" ' + selected + '>' + name + '</option>'
-                );
+                modalContent.find(self.selectorChosenField).append($option);
               });
             }
             var config = {
index ceddf54..79edfab 100644 (file)
@@ -174,7 +174,7 @@ define([
       });
       this.chosenField.prop('disabled', false);
       $.each(tagArray, function(i, tag) {
-        self.chosenField.append('<option>' + tag + '</option>');
+        self.chosenField.append($('<option>').text(tag));
       });
       this.chosenField.trigger('chosen:updated');
     },
index 6b82d36..81fdda8 100644 (file)
@@ -21,11 +21,14 @@ define([
     'TYPO3/CMS/Install/ProgressBar',
     'TYPO3/CMS/Install/InfoBox',
     'TYPO3/CMS/Install/Severity',
-    'TYPO3/CMS/Backend/Notification'
+    'TYPO3/CMS/Backend/Notification',
+    'TYPO3/CMS/Core/SecurityUtility'
   ],
-  function ($, Router, FlashMessage, ProgressBar, InfoBox, Severity, Notification) {
+  function ($, Router, FlashMessage, ProgressBar, InfoBox, Severity, Notification, SecurityUtility) {
     'use strict';
 
+    var securityUtility = new SecurityUtility();
+
     return {
       selectorModalBody: '.t3js-modal-body',
       selectorModuleContent: '.t3js-module-content',
@@ -177,17 +180,22 @@ define([
                 var adds = modalContent.find(self.selectorWizardsBlockingAddsTemplate).clone();
                 if (typeof(data.adds.tables) === 'object') {
                   data.adds.tables.forEach(function (element) {
-                    adds.find(self.selectorWizardsBlockingAddsRows).append('Table: ' + element.table + '<br>');
+                    var title = 'Table: ' + securityUtility.encodeHtml(element.table);
+                    adds.find(self.selectorWizardsBlockingAddsRows).append(title, '<br>');
                   });
                 }
                 if (typeof(data.adds.columns) === 'object') {
                   data.adds.columns.forEach(function (element) {
-                    adds.find(self.selectorWizardsBlockingAddsRows).append('Table: ' + element.table + ', Field: ' + element.field + '<br>');
+                    var title = 'Table: ' + securityUtility.encodeHtml(element.table)
+                      + ', Field: ' + securityUtility.encodeHtml(element.field);
+                    adds.find(self.selectorWizardsBlockingAddsRows).append(title, '<br>');
                   });
                 }
                 if (typeof(data.adds.indexes) === 'object') {
                   data.adds.indexes.forEach(function (element) {
-                    adds.find(self.selectorWizardsBlockingAddsRows).append('Table: ' + element.table + ', Index: ' + element.index + '<br>');
+                    var title = 'Table: ' + securityUtility.encodeHtml(element.table)
+                      + ', Index: ' + securityUtility.encodeHtml(element.index);
+                    adds.find(self.selectorWizardsBlockingAddsRows).append(title, '<br>');
                   });
                 }
                 modalContent.find(self.selectorOutputWizardsContainer).append(adds);