1 Upstream patch for CVE-2014-0157. This issue is fixed in Icehouse |
|
2 2014.1 and Havana 2013.2.4. |
|
3 |
|
4 From 54ec015f720a4379e8ffc34345b3a7bf36b6f15b Mon Sep 17 00:00:00 2001 |
|
5 From: CristianFiorentino <[email protected]> |
|
6 Date: Mon, 10 Mar 2014 17:36:31 -0300 |
|
7 Subject: [PATCH] Introduces escaping in Horizon/Orchestration |
|
8 |
|
9 1) Escape help_text a second time to avoid bootstrap tooltip XSS issue |
|
10 |
|
11 The "Description" parameter in a Heat template is used to populate |
|
12 a help_text tooltip in the dynamically generated Heat form. Bootstrap |
|
13 inserts this tooltip into the DOM using .html() which undoes any |
|
14 escaping we do in Django (it should be using .text()). |
|
15 |
|
16 This was fixed by forcing the help_text content to be escaped a second |
|
17 time. The issue itself is mitigated in bootstrap.js release 2.0.3 |
|
18 (ours is currently 2.0.1). |
|
19 |
|
20 2) Properly escape untrusted Heat template 'outputs' |
|
21 |
|
22 The 'outputs' parameter in a Heat template was included in a Django |
|
23 template with HTML autoescaping turned off. Malicious HTML content |
|
24 could be included in a Heat template and would be rendered by Horizon |
|
25 when details about a created stack were displayed. |
|
26 |
|
27 This was fixed by not disabling autoescaping and explicitly escaping |
|
28 untrusted values in any strings that are later marked "safe" to render |
|
29 without further escaping. |
|
30 |
|
31 Conflicts: |
|
32 openstack_dashboard/dashboards/project/stacks/mappings.py |
|
33 |
|
34 Change-Id: Icd9f9d9ca77068b12227d77469773a325c840001 |
|
35 Closes-Bug: #1289033 |
|
36 Co-Authored-By: Kieran Spear <[email protected]> |
|
37 --- |
|
38 horizon/templates/horizon/common/_form_fields.html | 7 ++++++- |
|
39 .../dashboards/project/stacks/mappings.py | 10 ++++++++-- |
|
40 .../stacks/templates/stacks/_detail_overview.html | 3 +-- |
|
41 .../dashboards/project/stacks/tests.py | 17 +++++++++++------ |
|
42 4 files changed, 26 insertions(+), 11 deletions(-) |
|
43 |
|
44 diff --git a/horizon/templates/horizon/common/_form_fields.html b/horizon/templates/horizon/common/_form_fields.html |
|
45 index 3567614..f6fb98f 100644 |
|
46 --- a/horizon/templates/horizon/common/_form_fields.html |
|
47 +++ b/horizon/templates/horizon/common/_form_fields.html |
|
48 @@ -14,7 +14,12 @@ |
|
49 <span class="help-inline">{{ error }}</span> |
|
50 {% endfor %} |
|
51 {% endif %} |
|
52 - <span class="help-block">{{ field.help_text }}</span> |
|
53 + {% comment %} |
|
54 + Escape help_text a second time here, to avoid an XSS issue in bootstrap.js. |
|
55 + This can most likely be removed once we upgrade bootstrap.js past 2.0.2. |
|
56 + Note: the spaces are necessary here. |
|
57 + {% endcomment %} |
|
58 + <span class="help-block">{% filter force_escape %} {{ field.help_text }} {% endfilter %} </span> |
|
59 <div class="input"> |
|
60 {{ field }} |
|
61 </div> |
|
62 diff --git a/openstack_dashboard/dashboards/project/stacks/mappings.py b/openstack_dashboard/dashboards/project/stacks/mappings.py |
|
63 index 0353291..f1389c5 100644 |
|
64 --- a/openstack_dashboard/dashboards/project/stacks/mappings.py |
|
65 +++ b/openstack_dashboard/dashboards/project/stacks/mappings.py |
|
66 @@ -19,6 +19,8 @@ import urlparse |
|
67 |
|
68 from django.core.urlresolvers import reverse # noqa |
|
69 from django.template.defaultfilters import register # noqa |
|
70 +from django.utils import html |
|
71 +from django.utils import safestring |
|
72 |
|
73 from openstack_dashboard.api import swift |
|
74 |
|
75 @@ -76,11 +78,15 @@ def stack_output(output): |
|
76 if not output: |
|
77 return u'' |
|
78 if isinstance(output, dict) or isinstance(output, list): |
|
79 - return u'<pre>%s</pre>' % json.dumps(output, indent=2) |
|
80 + json_string = json.dumps(output, indent=2) |
|
81 + safe_output = u'<pre>%s</pre>' % html.escape(json_string) |
|
82 + return safestring.mark_safe(safe_output) |
|
83 if isinstance(output, basestring): |
|
84 parts = urlparse.urlsplit(output) |
|
85 if parts.netloc and parts.scheme in ('http', 'https'): |
|
86 - return u'<a href="%s" target="_blank">%s</a>' % (output, output) |
|
87 + url = html.escape(output) |
|
88 + safe_link = u'<a href="%s" target="_blank">%s</a>' % (url, url) |
|
89 + return safestring.mark_safe(safe_link) |
|
90 return unicode(output) |
|
91 |
|
92 |
|
93 diff --git a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html |
|
94 index f4756e0..33fe783 100644 |
|
95 --- a/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html |
|
96 +++ b/openstack_dashboard/dashboards/project/stacks/templates/stacks/_detail_overview.html |
|
97 @@ -36,9 +36,8 @@ |
|
98 <dt>{{ output.output_key }}</dt> |
|
99 <dd>{{ output.description }}</dd> |
|
100 <dd> |
|
101 - {% autoescape off %} |
|
102 {{ output.output_value|stack_output }} |
|
103 - {% endautoescape %}</dd> |
|
104 + </dd> |
|
105 {% endfor %} |
|
106 </dl> |
|
107 </div> |
|
108 diff --git a/openstack_dashboard/dashboards/project/stacks/tests.py b/openstack_dashboard/dashboards/project/stacks/tests.py |
|
109 index 408d86f..986e3e0 100644 |
|
110 --- a/openstack_dashboard/dashboards/project/stacks/tests.py |
|
111 +++ b/openstack_dashboard/dashboards/project/stacks/tests.py |
|
112 @@ -16,6 +16,7 @@ import json |
|
113 |
|
114 from django.core.urlresolvers import reverse # noqa |
|
115 from django import http |
|
116 +from django.utils import html |
|
117 |
|
118 from mox import IsA # noqa |
|
119 |
|
120 @@ -77,12 +78,16 @@ class MappingsTests(test.TestCase): |
|
121 self.assertEqual(u'foo', mappings.stack_output('foo')) |
|
122 self.assertEqual(u'', mappings.stack_output(None)) |
|
123 |
|
124 - self.assertEqual( |
|
125 - u'<pre>[\n "one", \n "two", \n "three"\n]</pre>', |
|
126 - mappings.stack_output(['one', 'two', 'three'])) |
|
127 - self.assertEqual( |
|
128 - u'<pre>{\n "foo": "bar"\n}</pre>', |
|
129 - mappings.stack_output({'foo': 'bar'})) |
|
130 + outputs = ['one', 'two', 'three'] |
|
131 + expected_text = """[\n "one", \n "two", \n "three"\n]""" |
|
132 + |
|
133 + self.assertEqual(u'<pre>%s</pre>' % html.escape(expected_text), |
|
134 + mappings.stack_output(outputs)) |
|
135 + |
|
136 + outputs = {'foo': 'bar'} |
|
137 + expected_text = """{\n "foo": "bar"\n}""" |
|
138 + self.assertEqual(u'<pre>%s</pre>' % html.escape(expected_text), |
|
139 + mappings.stack_output(outputs)) |
|
140 |
|
141 self.assertEqual( |
|
142 u'<a href="http://www.example.com/foo" target="_blank">' |
|
143 -- |
|
144 1.7.9.5 |
|
145 |
|