CVE-2014-125078: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in yanheven console and classified as problematic. Affected by this issue is some unknown functionality of the file horizon/static/horizon/js/horizon.instances.js. The manipulation leads to cross site scripting. The attack may be launched remotely. The patch is identified as 32a7b713468161282f2ea01d5e2faff980d924cd. It is recommended to apply a patch to fix this issue. VDB-218354 is the identifier assigned to this vulnerability.
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-01-15T09:15Z
Repository: https://github.com/yanheven/console
Language: Python
Files changed: horizon/static/horizon/js/horizon.instances.js, horizon/tables/base.py, openstack_dashboard/dashboards/admin/groups/tables.py, openstack_dashboard/dashboards/admin/users/tables.py, openstack_dashboard/dashboards/project/stacks/tables.py, openstack_dashboard/dashboards/project/stacks/tabs.py
Commit date: 2014-07-08 13:08:15 +0100
Commit message: Fix multiple Cross-Site Scripting (XSS) vulnerabilities.

 * Ensure user emails are properly escaped

User emails in the Users and Groups panel are being passed through the
urlize filter to transform them into clickable links. However, urlize
expects input to be already escaped and safe. We should make sure to
escape the strings first as email addresses are not validated and can
contain any type of string.

Closes-Bug: #1320235

 * Ensure network names are properly escaped in the Launch Instance menu

Closes-Bug: #1322197

 * Escape the URLs generated for the Horizon tables

When generating the Horizon tables, there was an assumption that only
the anchor text needed to be escaped. However some URLs are generated
based on user-provided data and should be escaped as well. Also escape
the link attributes for good measure.

 * Use 'reverse' to generate the Resource URLs in the stacks tables

Closes-Bug: #1308727

Change-Id: Ic8a92e69f66c2d265a802f350e30f091181aa42e

Diff: diff --git a/horizon/static/horizon/js/horizon.instances.js b/horizon/static/horizon/js/horizon.instances.js
index e8e9353b..d4ef8a0a 100644
--- a/horizon/static/horizon/js/horizon.instances.js
+++ b/horizon/static/horizon/js/horizon.instances.js
@@ -49,12 +49,19 @@ horizon.instances = {
     horizon.instances.networks_selected = [];
     horizon.instances.networks_available = [];
     $(this.get_network_element("")).each(function(){
       var $this = $(this);
       var $input = $this.children("input");
+      var name = $this.text().replace(/^\s+/,"")
+                             .replace(/&/g, '&amp;')
+                             .replace(/</g, '&lt;')
+                             .replace(/>/g, '&gt;')
+                             .replace(/"/g, '&quot;')
+                             .replace(/'/g, '&#x27;')
+                             .replace(/\//g, '&#x2F;');
       var network_property = {
-        name:$this.text().replace(/^\s+/,""),
+        name:name,
         id:$input.attr("id"),
         value:$input.attr("value")
       };
       if($input.is(':checked')) {
         horizon.instances.networks_selected.push(network_property);
diff --git a/horizon/tables/base.py b/horizon/tables/base.py
index 10aaa98d..4aceb81d 100644
--- a/horizon/tables/base.py
+++ b/horizon/tables/base.py
@@ -674,11 +674,13 @@ class Cell(html.HTMLElement):
             raise template.TemplateSyntaxError, exc_info[1], exc_info[2]
         if self.url:
             link_classes = ' '.join(self.column.link_classes)
             # Escape the data inside while allowing our HTML to render
             data = mark_safe('<a href="%s" class="%s">%s</a>' %
-                             (self.url, link_classes, escape(unicode(data))))
+                             (escape(self.url),
+                              escape(link_classes),
+                              escape(unicode(data))))
         return data
 
     @property
     def url(self):
         if self.column.link:
diff --git a/openstack_dashboard/dashboards/admin/groups/tables.py b/openstack_dashboard/dashboards/admin/groups/tables.py
index 1f32da2c..286c22b4 100644
--- a/openstack_dashboard/dashboards/admin/groups/tables.py
+++ b/openstack_dashboard/dashboards/admin/groups/tables.py
@@ -159,11 +159,12 @@ class AddMembersLink(tables.LinkAction):
 
 
 class UsersTable(tables.DataTable):
     name = tables.Column('name', verbose_name=_('User Name'))
     email = tables.Column('email', verbose_name=_('Email'),
-                          filters=[defaultfilters.urlize])
+                          filters=[defaultfilters.escape,
+                                   defaultfilters.urlize])
     id = tables.Column('id', verbose_name=_('User ID'))
     enabled = tables.Column('enabled', verbose_name=_('Enabled'),
                             status=True,
                             status_choices=STATUS_CHOICES,
                             empty_value="False")
diff --git a/openstack_dashboard/dashboards/admin/users/tables.py b/openstack_dashboard/dashboards/admin/users/tables.py
index b2032c48..9c6dc04c 100644
--- a/openstack_dashboard/dashboards/admin/users/tables.py
+++ b/openstack_dashboard/dashboards/admin/users/tables.py
@@ -129,11 +129,13 @@ class UsersTable(tables.DataTable):
     )
     name = tables.Column('name', verbose_name=_('User Name'))
     email = tables.Column('email', verbose_name=_('Email'),
                           filters=(lambda v: defaultfilters
                                    .default_if_none(v, ""),
-                                   defaultfilters.urlize))
+                                   defaultfilters.escape,
+                                   defaultfilters.urlize)
+                          )
     # Default tenant is not returned from Keystone currently.
     #default_tenant = tables.Column('default_tenant',
     #                               verbose_name=_('Default Project'))
     id = tables.Column('id', verbose_name=_('User ID'))
     enabled = tables.Column('enabled', verbose_name=_('Enabled'),
diff --git a/openstack_dashboard/dashboards/project/stacks/tables.py b/openstack_dashboard/dashboards/project/stacks/tables.py
index e5f829ac..11747468 100644
--- a/openstack_dashboard/dashboards/project/stacks/tables.py
+++ b/openstack_dashboard/dashboards/project/stacks/tables.py
@@ -112,15 +112,20 @@ class StacksTable(tables.DataTable):
         table_actions = (LaunchStack, DeleteStack,)
         row_actions = (DeleteStack,
                        ChangeStackTemplate)
 
 
+def get_resource_url(obj):
+    return urlresolvers.reverse('horizon:project:stacks:resource',
+                                args=(obj.stack_id, obj.resource_name))
+
+
 class EventsTable(tables.DataTable):
 
     logical_resource = tables.Column('resource_name',
                                      verbose_name=_("Stack Resource"),
-                                     link=lambda d: d.resource_name,)
+                                     link=get_resource_url)
     physical_resource = tables.Column('physical_resource_id',
                                       verbose_name=_("Resource"),
                                       link=mappings.resource_to_url)
     timestamp = tables.Column('event_time',
                               verbose_name=_("Time Since Event"),
@@ -161,11 +166,11 @@ class ResourcesTable(tables.DataTable):
         ("Create Failed", False),
     )
 
     logical_resource = tables.Column('resource_name',
                                      verbose_name=_("Stack Resource"),
-                                     link=lambda d: d.resource_name)
+                                     link=get_resource_url)
     physical_resource = tables.Column('physical_resource_id',
                                      verbose_name=_("Resource"),
                                      link=mappings.resource_to_url)
     resource_type = tables.Column("resource_type",
                            verbose_name=_("Stack Resource Type"),)
diff --git a/openstack_dashboard/dashboards/project/stacks/tabs.py b/openstack_dashboard/dashboards/project/stacks/tabs.py
index c68464a3..976541a0 100644
--- a/openstack_dashboard/dashboards/project/stacks/tabs.py
+++ b/openstack_dashboard/dashboards/project/stacks/tabs.py
@@ -77,10 +77,13 @@ class StackEventsTab(tabs.Tab):
         stack = self.tab_group.kwargs['stack']
         try:
             stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
             events = api.heat.events_list(self.request, stack_identifier)
             LOG.debug('got events %s' % events)
+            # The stack id is needed to generate the resource URL.
+            for event in events:
+                event.stack_id = stack.id
         except Exception:
             events = []
             messages.error(request, _(
                 'Unable to get events for stack "%s".') % stack.stack_name)
         return {"stack": stack,
@@ -97,10 +100,13 @@ class StackResourcesTab(tabs.Tab):
         stack = self.tab_group.kwargs['stack']
         try:
             stack_identifier = '%s/%s' % (stack.stack_name, stack.id)
             resources = api.heat.resources_list(self.request, stack_identifier)
             LOG.debug('got resources %s' % resources)
+            # The stack id is needed to generate the resource URL.
+            for r in resources:
+                r.stack_id = stack.id
         except Exception:
             resources = []
             messages.error(request, _(
                 'Unable to get resources for stack "%s".') % stack.stack_name)
         return {"stack": stack,

Vulnerable code:         name:$this.text().replace(/^\s+/,""),
                             (self.url, link_classes, escape(unicode(data))))
                          filters=[defaultfilters.urlize])
                                   defaultfilters.urlize))
                                     link=lambda d: d.resource_name,)
                                     link=lambda d: d.resource_name)
Fixed code:       var name = $this.text().replace(/^\s+/,"")
                             .replace(/&/g, '&amp;')
                             .replace(/</g, '&lt;')
                             .replace(/>/g, '&gt;')
                             .replace(/"/g, '&quot;')
                             .replace(/'/g, '&#x27;')
                             .replace(/\//g, '&#x2F;');
        name:name,
                             (escape(self.url),
                              escape(link_classes),
                              escape(unicode(data))))
                          filters=[defaultfilters.escape,
                                   defaultfilters.urlize])
                                   defaultfilters.escape,
                                   defaultfilters.urlize)
                          )
def get_resource_url(obj):
    return urlresolvers.reverse('horizon:project:stacks:resource',
                                args=(obj.stack_id, obj.resource_name))


                                     link=get_resource_url)
                                     link=get_resource_url)
            # The stack id is needed to generate the resource URL.
            for event in events:
                event.stack_id = stack.id
            # The stack id is needed to generate the resource URL.
            for r in resources:
                r.stack_id = stack.id