CVE-2018-12043: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: content/content.blueprintspages.php in Symphony 2.7.6 has XSS via the pages content page.
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: 2018-06-07T20:29Z
Repository: https://github.com/symphonycms/symphony-2
Language: PHP
Files changed: symphony/content/content.blueprintspages.php
Commit date: 2018-06-06 10:21:03 -0400
Commit message: Fix possible self-XSS in the pages content page

Reported by Linkey <2542566937@qq.com>

Diff: diff --git a/symphony/content/content.blueprintspages.php b/symphony/content/content.blueprintspages.php
index 840e1671c..8d66ea8a7 100644
--- a/symphony/content/content.blueprintspages.php
+++ b/symphony/content/content.blueprintspages.php
@@ -28,26 +28,26 @@ class contentBlueprintsPages extends AdministrationPage
         foreach ($pages as &$page) {
             // If we are viewing the Page Editor, the Breadcrumbs should link
             // to the parent's Page Editor.
             if ($this->_context[0] == 'edit') {
                 $page = Widget::Anchor(
-                    PageManager::fetchTitleFromHandle($page),
+                    General::sanitize(PageManager::fetchTitleFromHandle($page)),
                     SYMPHONY_URL . '/blueprints/pages/edit/' . PageManager::fetchIDFromHandle($page) . '/'
                 );
 
                 // If the pages index is nested, the Breadcrumb should link to the
                 // Pages Index filtered by parent
             } elseif (Symphony::Configuration()->get('pages_table_nest_children', 'symphony') == 'yes') {
                 $page = Widget::Anchor(
-                    PageManager::fetchTitleFromHandle($page),
+                    General::sanitize(PageManager::fetchTitleFromHandle($page)),
                     SYMPHONY_URL . '/blueprints/pages/?parent=' . PageManager::fetchIDFromHandle($page)
                 );
 
                 // If there is no nesting on the Pages Index, the breadcrumb is
                 // not a link, just plain text
             } else {
-                $page = new XMLElement('span', PageManager::fetchTitleFromHandle($page));
+                $page = new XMLElement('span', General::sanitize(PageManager::fetchTitleFromHandle($page)));
             }
         }
 
         if (!$preserve_last) {
             array_pop($pages);
@@ -68,14 +68,23 @@ class contentBlueprintsPages extends AdministrationPage
 
         if ($nesting && isset($_GET['parent']) && is_numeric($_GET['parent'])) {
             $parent = PageManager::fetchPageByID((int)$_GET['parent'], array('title', 'id'));
         }
 
-        $this->appendSubheading(isset($parent) ? $parent['title'] : __('Pages'), Widget::Anchor(
-            __('Create New'), Administration::instance()->getCurrentPageURL() . 'new/' . ($nesting && isset($parent) ? "?parent={$parent['id']}" : null),
-            __('Create a new page'), 'create button', null, array('accesskey' => 'c')
-        ));
+        $this->appendSubheading(
+            isset($parent)
+                ? General::sanitize($parent['title'])
+                : __('Pages'),
+            Widget::Anchor(
+                __('Create New'),
+                Administration::instance()->getCurrentPageURL() . 'new/' . ($nesting && isset($parent) ? "?parent={$parent['id']}" : null),
+                __('Create a new page'),
+                'create button',
+                null,
+                ['accesskey' => 'c']
+            )
+        );
 
         if (isset($parent)) {
             $this->insertBreadcrumbsUsingPageIdentifier($parent['id'], false);
         }
 
@@ -110,30 +119,38 @@ class contentBlueprintsPages extends AdministrationPage
                 $page_title = ($nesting ? $page['title'] : PageManager::resolvePageTitle($page['id']));
                 $page_url = URL . '/' . PageManager::resolvePagePath($page['id']) . '/';
                 $page_edit_url = Administration::instance()->getCurrentPageURL() . 'edit/' . $page['id'] . '/';
                 $page_template = PageManager::createFilePath($page['path'], $page['handle']);
 
-                $col_title = Widget::TableData(Widget::Anchor($page_title, $page_edit_url, $page['handle']));
-                $col_title->appendChild(Widget::Label(__('Select Page %s', array($page_title)), null, 'accessible', null, array(
-                    'for' => 'page-' . $page['id']
-                )));
+                $col_title = Widget::TableData(
+                    Widget::Anchor(General::sanitize($page_title), $page_edit_url, $page['handle'])
+                );
+                $col_title->appendChild(
+                    Widget::Label(
+                        __('Select Page %s', [General::sanitize($page_title)]),
+                        null,
+                        'accessible',
+                        null,
+                        ['for' => 'page-' . $page['id']]
+                    )
+                );
                 $col_title->appendChild(Widget::Input('items['.$page['id'].']', 'on', 'checkbox', array(
                     'id' => 'page-' . $page['id']
                 )));
 
                 $col_template = Widget::TableData($page_template . '.xsl');
 
                 $col_url = Widget::TableData(Widget::Anchor($page_url, $page_url));
 
                 if ($page['params']) {
-                    $col_params = Widget::TableData(trim($page['params'], '/'));
+                    $col_params = Widget::TableData(trim(General::sanitize($page['params']), '/'));
                 } else {
                     $col_params = Widget::TableData(__('None'), 'inactive');
                 }
 
                 if (!empty($page['type'])) {
-                    $col_types = Widget::TableData(implode(', ', $page['type']));
+                    $col_types = Widget::TableData(implode(', ', array_map(['General', 'sanitize'], $page['type'])));
                 } else {
                     $col_types = Widget::TableData(__('None'), 'inactive');
                 }
 
                 if (in_array($page['id'], $this->_hilights)) {
@@ -292,33 +309,41 @@ class contentBlueprintsPages extends AdministrationPage
 
         if (trim($title) == '') {
             $title = $existing['title'];
         }
 
-        $this->setTitle(__(
-            ($title ? '%1$s &ndash; %2$s &ndash; %3$s' : '%2$s &ndash; %3$s'),
-            array(
-                $title,
-                __('Pages'),
-                __('Symphony')
+        $this->setTitle(
+            __(
+                $title
+                    ? '%1$s &ndash; %2$s &ndash; %3$s'
+                    : '%2$s &ndash; %3$s',
+                [General::sanitize($title), __('Pages'), __('Symphony')]
             )
-        ));
+        );
         $this->addElementToHead(new XMLElement('link', null, array(
             'rel' => 'canonical',
             'href' => SYMPHONY_URL . $canonical_link,
         )));
 
         $page_id = isset($page_id) ? $page_id : null;
 
         if (!empty($title)) {
             $page_url = URL . '/' . PageManager::resolvePagePath($page_id) . '/';
 
-            $this->appendSubheading($title, array(
-                Widget::Anchor(__('View Page'), $page_url, __('View Page on Frontend'), 'button', null, array('target' => '_blank', 'accesskey' => 'v'))
-            ));
+            $this->appendSubheading(
+                General::sanitize($title),
+                Widget::Anchor(
+                    __('View Page'),
+                    $page_url,
+                    __('View Page on Frontend'),
+                    'button',
+                    null,
+                    ['target' => '_blank', 'accesskey' => 'v']
+                )
+            );
         } else {
-            $this->appendSubheading(!empty($title) ? $title : __('Untitled'));
+            $this->appendSubheading(!empty($title) ? General::sanitize($title) : __('Untitled'));
         }
 
         if (isset($page_id)) {
             $this->insertBreadcrumbsUsingPageIdentifier($page_id, false);
         } else {
@@ -418,11 +443,11 @@ class contentBlueprintsPages extends AdministrationPage
         $tags->setAttribute('data-interactive', 'data-interactive');
 
         $types = PageManager::fetchAvailablePageTypes();
 
         foreach ($types as $type) {
-            $tags->appendChild(new XMLElement('li', $type));
+            $tags->appendChild(new XMLElement('li', General::sanitize($type)));
         }
 
         $column->appendChild($tags);
         $group->appendChild($column);
         $fieldset->appendChild($group);
@@ -507,12 +532,14 @@ class contentBlueprintsPages extends AdministrationPage
         );
 
         $div = new XMLElement('div');
         $div->setAttribute('class', 'actions');
         $div->appendChild(Widget::Input(
-            'action[save]', ($this->_context[0] == 'edit' ? __('Save Changes') : __('Create Page')),
-            'submit', array('accesskey' => 's')
+            'action[save]',
+            ($this->_context[0] == 'edit' ? __('Save Changes') : __('Create Page')),
+            'submit',
+            ['accesskey' => 's']
         ));
 
         if ($this->_context[0] == 'edit') {
             $button = new XMLElement('button', __('Delete'));
             $button->setAttributeArray(array('name' => 'action[delete]', 'class' => 'button confirm delete', 'title' => __('Delete this page'), 'accesskey' => 'd', 'data-message' => __('Are you sure you want to delete this page?')));

Vulnerable code:                     PageManager::fetchTitleFromHandle($page),
                    PageManager::fetchTitleFromHandle($page),
                $page = new XMLElement('span', PageManager::fetchTitleFromHandle($page));
        $this->appendSubheading(isset($parent) ? $parent['title'] : __('Pages'), Widget::Anchor(
            __('Create New'), Administration::instance()->getCurrentPageURL() . 'new/' . ($nesting && isset($parent) ? "?parent={$parent['id']}" : null),
            __('Create a new page'), 'create button', null, array('accesskey' => 'c')
        ));
                $col_title = Widget::TableData(Widget::Anchor($page_title, $page_edit_url, $page['handle']));
                $col_title->appendChild(Widget::Label(__('Select Page %s', array($page_title)), null, 'accessible', null, array(
                    'for' => 'page-' . $page['id']
                )));
                    $col_params = Widget::TableData(trim($page['params'], '/'));
                    $col_types = Widget::TableData(implode(', ', $page['type']));
        $this->setTitle(__(
            ($title ? '%1$s &ndash; %2$s &ndash; %3$s' : '%2$s &ndash; %3$s'),
            array(
                $title,
                __('Pages'),
                __('Symphony')
        ));
            $this->appendSubheading($title, array(
                Widget::Anchor(__('View Page'), $page_url, __('View Page on Frontend'), 'button', null, array('target' => '_blank', 'accesskey' => 'v'))
            ));
            $this->appendSubheading(!empty($title) ? $title : __('Untitled'));
            $tags->appendChild(new XMLElement('li', $type));
            'action[save]', ($this->_context[0] == 'edit' ? __('Save Changes') : __('Create Page')),
            'submit', array('accesskey' => 's')
Fixed code:                     General::sanitize(PageManager::fetchTitleFromHandle($page)),
                    General::sanitize(PageManager::fetchTitleFromHandle($page)),
                $page = new XMLElement('span', General::sanitize(PageManager::fetchTitleFromHandle($page)));
        $this->appendSubheading(
            isset($parent)
                ? General::sanitize($parent['title'])
                : __('Pages'),
            Widget::Anchor(
                __('Create New'),
                Administration::instance()->getCurrentPageURL() . 'new/' . ($nesting && isset($parent) ? "?parent={$parent['id']}" : null),
                __('Create a new page'),
                'create button',
                null,
                ['accesskey' => 'c']
            )
        );
                $col_title = Widget::TableData(
                    Widget::Anchor(General::sanitize($page_title), $page_edit_url, $page['handle'])
                );
                $col_title->appendChild(
                    Widget::Label(
                        __('Select Page %s', [General::sanitize($page_title)]),
                        null,
                        'accessible',
                        null,
                        ['for' => 'page-' . $page['id']]
                    )
                );
                    $col_params = Widget::TableData(trim(General::sanitize($page['params']), '/'));
                    $col_types = Widget::TableData(implode(', ', array_map(['General', 'sanitize'], $page['type'])));
        $this->setTitle(
            __(
                $title
                    ? '%1$s &ndash; %2$s &ndash; %3$s'
                    : '%2$s &ndash; %3$s',
                [General::sanitize($title), __('Pages'), __('Symphony')]
        );
            $this->appendSubheading(
                General::sanitize($title),
                Widget::Anchor(
                    __('View Page'),
                    $page_url,
                    __('View Page on Frontend'),
                    'button',
                    null,
                    ['target' => '_blank', 'accesskey' => 'v']
                )
            );
            $this->appendSubheading(!empty($title) ? General::sanitize($title) : __('Untitled'));
            $tags->appendChild(new XMLElement('li', General::sanitize($type)));
            'action[save]',
            ($this->_context[0] == 'edit' ? __('Save Changes') : __('Create Page')),
            'submit',
            ['accesskey' => 's']