CVE-2017-8876: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Symphony 2 2.6.11 has XSS in the meta[navigation_group] parameter to content/content.blueprintssections.php.
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: 2017-05-10T05:29Z
Repository: https://github.com/DeuxHuitHuit/symphony-2
Language: PHP
Files changed: symphony/content/content.blueprintssections.php, symphony/content/content.publish.php, symphony/lib/toolkit/class.administrationpage.php
Commit date: 2017-05-09 11:29:30 -0400
Commit message: Prevent XSS with section's name and nav group

This commit adds sanitization of the section's name and naviguation
group, which permitted authenticated XSS.

Reported by Pradeep Kumar <pradeepch99@gmail.com>

cc @michael-e @brendo

Diff: diff --git a/symphony/content/content.blueprintssections.php b/symphony/content/content.blueprintssections.php
index 0521ee1a..115f23ee 100644
--- a/symphony/content/content.blueprintssections.php
+++ b/symphony/content/content.blueprintssections.php
@@ -213,11 +213,11 @@ class contentBlueprintsSections extends AdministrationPage
 
         $navgroupdiv = new XMLElement('div', null, array('class' => 'column'));
 
         $sections = SectionManager::fetch(null, 'ASC', 'sortorder');
         $label = Widget::Label(__('Navigation Group'));
-        $label->appendChild(Widget::Input('meta[navigation_group]', $meta['navigation_group']));
+        $label->appendChild(Widget::Input('meta[navigation_group]', (isset($meta['navigation_group']) ? General::sanitize($meta['navigation_group']) : null)));
 
         if (isset($this->_errors['navigation_group'])) {
             $navgroupdiv->appendChild(Widget::Error($label, $this->_errors['navigation_group']));
         } else {
             $navgroupdiv->appendChild($label);
@@ -230,11 +230,11 @@ class contentBlueprintsSections extends AdministrationPage
             foreach ($sections as $s) {
                 if (in_array($s->get('navigation_group'), $groups)) {
                     continue;
                 }
 
-                $ul->appendChild(new XMLElement('li', $s->get('navigation_group')));
+                $ul->appendChild(new XMLElement('li', General::sanitize($s->get('navigation_group'))));
                 $groups[] = $s->get('navigation_group');
             }
 
             $navgroupdiv->appendChild($ul);
         }
@@ -379,12 +379,12 @@ class contentBlueprintsSections extends AdministrationPage
                 $meta['name'] = $section->get('name');
             }
         }
 
         $this->setPageType('form');
-        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($meta['name'], __('Sections'), __('Symphony'))));
-        $this->appendSubheading($meta['name'],
+        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array(General::sanitize($meta['name']), __('Sections'), __('Symphony'))));
+        $this->appendSubheading(General::sanitize($meta['name']),
             Widget::Anchor(__('View Entries'), SYMPHONY_URL . '/publish/' . $section->get('handle'), __('View Section Entries'), 'button')
         );
         $this->insertBreadcrumbs(array(
             Widget::Anchor(__('Sections'), SYMPHONY_URL . '/blueprints/sections/'),
         ));
@@ -423,11 +423,11 @@ class contentBlueprintsSections extends AdministrationPage
 
         $navgroupdiv = new XMLElement('div', null, array('class' => 'column'));
 
         $sections = SectionManager::fetch(null, 'ASC', 'sortorder');
         $label = Widget::Label(__('Navigation Group'));
-        $label->appendChild(Widget::Input('meta[navigation_group]', $meta['navigation_group']));
+        $label->appendChild(Widget::Input('meta[navigation_group]', General::sanitize($meta['navigation_group'])));
 
         if (isset($this->_errors['navigation_group'])) {
             $navgroupdiv->appendChild(Widget::Error($label, $this->_errors['navigation_group']));
         } else {
             $navgroupdiv->appendChild($label);
@@ -440,11 +440,11 @@ class contentBlueprintsSections extends AdministrationPage
             foreach ($sections as $s) {
                 if (in_array($s->get('navigation_group'), $groups)) {
                     continue;
                 }
 
-                $ul->appendChild(new XMLElement('li', $s->get('navigation_group')));
+                $ul->appendChild(new XMLElement('li', General::sanitize($s->get('navigation_group'))));
                 $groups[] = $s->get('navigation_group');
             }
 
             $navgroupdiv->appendChild($ul);
         }
diff --git a/symphony/content/content.publish.php b/symphony/content/content.publish.php
index 3b893018..c0160bf9 100644
--- a/symphony/content/content.publish.php
+++ b/symphony/content/content.publish.php
@@ -377,11 +377,11 @@ class contentPublish extends AdministrationPage
         }
 
         $section = SectionManager::fetch($section_id);
 
         $this->setPageType('table');
-        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
+        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
 
         $filters = array();
         $filter_querystring = $prepopulate_querystring = $where = $joins = null;
         $current_page = (isset($_REQUEST['pg']) && is_numeric($_REQUEST['pg']) ? max(1, intval($_REQUEST['pg'])) : 1);
 
@@ -467,11 +467,11 @@ class contentPublish extends AdministrationPage
         // Only show the Edit Section button if the Author is a developer. #938 ^BA
         if (Symphony::Author()->isDeveloper()) {
             array_unshift($subheading_buttons, Widget::Anchor(__('Edit Section'), SYMPHONY_URL . '/blueprints/sections/edit/' . $section_id . '/', __('Edit Section Configuration'), 'button'));
         }
 
-        $this->appendSubheading($section->get('name'), $subheading_buttons);
+        $this->appendSubheading(General::sanitize($section->get('name')), $subheading_buttons);
 
         /**
          * Allows adjustments to be made to the SQL where and joins statements
          * before they are used to fetch the entries for the page
          *
@@ -958,11 +958,11 @@ class contentPublish extends AdministrationPage
         }
 
         $section = SectionManager::fetch($section_id);
 
         $this->setPageType('form');
-        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
+        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
 
         // Ensure errored entries still maintain any prepopulated values [#2211]
         $this->Form->setAttribute('action', $this->Form->getAttribute('action') . $this->getPrepopulateString());
         $this->Form->setAttribute('enctype', 'multipart/form-data');
 
@@ -984,11 +984,11 @@ class contentPublish extends AdministrationPage
             $this->appendSubheading(__('Untitled'));
         }
 
         // Build filtered breadcrumb [#1378}
         $this->insertBreadcrumbs(array(
-            Widget::Anchor($section->get('name'), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
+            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
         ));
 
         $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
 
         // If there is post data floating around, due to errors, create an entry object
@@ -1295,11 +1295,11 @@ class contentPublish extends AdministrationPage
             }
         }
 
         $this->setPageType('form');
         $this->Form->setAttribute('enctype', 'multipart/form-data');
-        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, $section->get('name'), __('Symphony'))));
+        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, General::sanitize($section->get('name')), __('Symphony'))));
 
         $sidebar_fields = $section->fetchFields(null, 'sidebar');
         $main_fields = $section->fetchFields(null, 'main');
 
         if (!empty($sidebar_fields) && !empty($main_fields)) {
@@ -1314,11 +1314,11 @@ class contentPublish extends AdministrationPage
         } else {
             $this->appendSubheading($title);
         }
 
         $this->insertBreadcrumbs(array(
-            Widget::Anchor($section->get('name'), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
+            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
         ));
 
         $this->Form->appendChild(Widget::Input('MAX_FILE_SIZE', Symphony::Configuration()->get('max_upload_size', 'admin'), 'hidden'));
 
         $primary = new XMLElement('fieldset');
diff --git a/symphony/lib/toolkit/class.administrationpage.php b/symphony/lib/toolkit/class.administrationpage.php
index dd82787d..bd9cafa3 100644
--- a/symphony/lib/toolkit/class.administrationpage.php
+++ b/symphony/lib/toolkit/class.administrationpage.php
@@ -805,11 +805,11 @@ class AdministrationPage extends HTMLPage
             if (isset($n['visible']) && $n['visible'] === 'no') {
                 continue;
             }
 
             if ($this->doesAuthorHaveAccess($n['limit'])) {
-                $xGroup = new XMLElement('li', $n['name'], array('role' => 'presentation'));
+                $xGroup = new XMLElement('li', General::sanitize($n['name']), array('role' => 'presentation'));
 
                 if (isset($n['class']) && trim($n['name']) !== '') {
                     $xGroup->setAttribute('class', $n['class']);
                 }
 
@@ -824,11 +824,11 @@ class AdministrationPage extends HTMLPage
                         }
 
                         if ($this->doesAuthorHaveAccess($c['limit'])) {
                             $xChild = new XMLElement('li');
                             $xChild->setAttribute('role', 'menuitem');
-                            $linkChild = Widget::Anchor($c['name'], SYMPHONY_URL . $c['link']);
+                            $linkChild = Widget::Anchor(General::sanitize($c['name']), SYMPHONY_URL . $c['link']);
                             if (isset($c['target'])) {
                                 $linkChild->setAttribute('target', $c['target']);
                             }
                             $xChild->appendChild($linkChild);
                             $xChildren->appendChild($xChild);

Vulnerable code:         $label->appendChild(Widget::Input('meta[navigation_group]', $meta['navigation_group']));
                $ul->appendChild(new XMLElement('li', $s->get('navigation_group')));
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($meta['name'], __('Sections'), __('Symphony'))));
        $this->appendSubheading($meta['name'],
        $label->appendChild(Widget::Input('meta[navigation_group]', $meta['navigation_group']));
                $ul->appendChild(new XMLElement('li', $s->get('navigation_group')));
        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
        $this->appendSubheading($section->get('name'), $subheading_buttons);
        $this->setTitle(__('%1$s &ndash; %2$s', array($section->get('name'), __('Symphony'))));
            Widget::Anchor($section->get('name'), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, $section->get('name'), __('Symphony'))));
            Widget::Anchor($section->get('name'), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
                $xGroup = new XMLElement('li', $n['name'], array('role' => 'presentation'));
                            $linkChild = Widget::Anchor($c['name'], SYMPHONY_URL . $c['link']);
Fixed code:         $label->appendChild(Widget::Input('meta[navigation_group]', (isset($meta['navigation_group']) ? General::sanitize($meta['navigation_group']) : null)));
                $ul->appendChild(new XMLElement('li', General::sanitize($s->get('navigation_group'))));
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array(General::sanitize($meta['name']), __('Sections'), __('Symphony'))));
        $this->appendSubheading(General::sanitize($meta['name']),
        $label->appendChild(Widget::Input('meta[navigation_group]', General::sanitize($meta['navigation_group'])));
                $ul->appendChild(new XMLElement('li', General::sanitize($s->get('navigation_group'))));
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
        $this->appendSubheading(General::sanitize($section->get('name')), $subheading_buttons);
        $this->setTitle(__('%1$s &ndash; %2$s', array(General::sanitize($section->get('name')), __('Symphony'))));
            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . '/publish/' . $this->_context['section_handle'] . '/' . $this->getFilterString()),
        $this->setTitle(__('%1$s &ndash; %2$s &ndash; %3$s', array($title, General::sanitize($section->get('name')), __('Symphony'))));
            Widget::Anchor(General::sanitize($section->get('name')), SYMPHONY_URL . (isset($filter_link) ? $filter_link : $base)),
                $xGroup = new XMLElement('li', General::sanitize($n['name']), array('role' => 'presentation'));
                            $linkChild = Widget::Anchor(General::sanitize($c['name']), SYMPHONY_URL . $c['link']);