CVE-2016-6519
Description
Cross-site scripting (XSS) vulnerability in the "Shares" overview in Openstack Manila before 2.5.1 allows remote authenticated users to inject arbitrary web script or HTML via the Metadata field in the "Create Share" form.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OpenStack Manila before 2.5.1 has a persistent XSS in the Metadata field of the Create Share form, allowing privilege escalation via cookie theft.
Vulnerability
A persistent cross-site scripting (XSS) vulnerability exists in OpenStack Manila (manila-ui) before version 2.5.1. The flaw resides in the 'Shares' overview page, where the Metadata field in the 'Create Share' form unsafely renders user-supplied input due to a mark_safe() call in tabs.py [1][2]. This allows authenticated users to inject arbitrary HTML or JavaScript that is later reflected in the share listing.
Exploitation
An authenticated but unprivileged user can exploit the vulnerability by crafting malicious payloads in the Metadata field when creating a share [2][3]. The injected script executes in the context of any other authenticated user who views the 'Shares' overview, without requiring any additional privileges or direct interaction beyond viewing the page.
Impact
Successful exploitation enables an attacker to steal session cookies from other authenticated users, leading to privilege escalation within the OpenStack dashboard [2][3]. The attacker can then impersonate the victim and perform actions with the victim's permissions, potentially gaining unauthorized access to sensitive data or resources.
Mitigation
The vulnerability is fixed in OpenStack Manila version 2.5.1 [1]. Red Hat issued security updates RHSA-2016:2115 (for Red Hat Enterprise Linux OpenStack Platform 7.0) and RHSA-2016:2117 (for Red Hat OpenStack Platform 9.0) on 2016-10-26 [3][4]. Users should upgrade to the patched version or apply the relevant vendor updates. No known workarounds are documented.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
manila-uiPyPI | < 2.5.1 | 2.5.1 |
Affected products
3- ghsa-coords2 versionspkg:pypi/manila-uipkg:rpm/suse/openstack-horizon-plugin-manila-ui&distro=SUSE%20OpenStack%20Cloud%206
< 2.5.1+ 1 more
- (no CPE)range: < 2.5.1
- (no CPE)range: < 1.2.1~a0~dev2-3.1
Patches
3009913d725beFix metadata_to_str function code injection vulnerability
5 files changed · +46 −20
manila_ui/dashboards/admin/shares/tabs.py+3 −5 modified@@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -25,6 +24,7 @@ from manila_ui.api import network from manila_ui.dashboards.admin.shares import tables from manila_ui.dashboards.admin.shares import utils +from manila_ui.dashboards import utils as common_utils class SnapshotsTab(tabs.TableTab): @@ -104,10 +104,8 @@ def get_share_types_data(self): _("Unable to retrieve share types")) # Convert dict with extra specs to friendly view for st in share_types: - es_str = "" - for k, v in st.extra_specs.iteritems(): - es_str += "%s=%s\r\n<br />" % (k, v) - st.extra_specs = mark_safe(es_str) + st.extra_specs = common_utils.metadata_to_str( + st.extra_specs, 8, 45) return share_types
manila_ui/dashboards/project/shares/shares/tables.py+1 −3 modified@@ -17,7 +17,6 @@ from django.core.urlresolvers import NoReverseMatch # noqa from django.core.urlresolvers import reverse from django.template.defaultfilters import title # noqa -from django.utils.safestring import mark_safe from django.utils.translation import string_concat, ugettext_lazy # noqa from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ @@ -150,8 +149,7 @@ def get_data(self, request, share_id): share.share_network = share_net.name or share_net.id else: share.share_network = None - meta_str = utils.metadata_to_str(share.metadata) - share.metadata = mark_safe(meta_str) + share.metadata = utils.metadata_to_str(share.metadata) return share
manila_ui/dashboards/project/shares/shares/tabs.py+4 −6 modified@@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -42,11 +41,10 @@ def get_shares_data(self): try: shares = manila.share_list(self.request) for share in shares: - share.share_network = \ - share_nets_names.get(share.share_network_id) or \ - share.share_network_id - meta_str = utils.metadata_to_str(share.metadata) - share.metadata = mark_safe(meta_str) + share.share_network = ( + share_nets_names.get(share.share_network_id) or + share.share_network_id) + share.metadata = utils.metadata_to_str(share.metadata) snapshots = manila.share_snapshot_list(self.request, detailed=True) share_ids_with_snapshots = []
manila_ui/dashboards/utils.py+18 −6 modified@@ -13,9 +13,23 @@ # under the License. from django.forms import ValidationError # noqa +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +html_escape_table = { + "&": "&", + '"': """, + "'": "'", + ">": ">", + "<": "<", +} + + +def html_escape(text): + return ''.join(html_escape_table.get(s, s) for s in text) + + def parse_str_meta(meta_s): """Parse multiline string with data from form. @@ -58,14 +72,12 @@ def parse_str_meta(meta_s): return set_dict, unset_list -def metadata_to_str(metadata): +def metadata_to_str(metadata, meta_visible_limit=4, text_length_limit=25): # Only convert dictionaries if not hasattr(metadata, 'keys'): return metadata - meta_visible_limit = 4 - text_length_limit = 25 meta = [] meta_keys = metadata.keys() meta_keys.sort() @@ -77,8 +89,8 @@ def metadata_to_str(metadata): v = metadata[k] if len(v) > text_length_limit: v = v[:text_length_limit] + '...' - meta.append("%s = %s" % (k_shortenned, v)) + meta.append("%s = %s" % (html_escape(k_shortenned), html_escape(v))) meta_str = "<br/>".join(meta) - if len(metadata.keys()) > meta_visible_limit: + if len(metadata.keys()) > meta_visible_limit and meta_str[-3:] != "...": meta_str += '...' - return meta_str + return mark_safe(meta_str)
manila_ui/test/dashboards/test_utils.py+20 −0 modified@@ -61,3 +61,23 @@ def test_parse_str_meta_success( ) def test_parse_str_meta_validation_error(self, input_data): self.assertRaises(ValidationError, utils.parse_str_meta, input_data) + + @ddt.data( + (({"a": "<script>alert('A')/*", "b": "*/</script>"}, ), + "a = <script>alert('A')/*<br/>b = */</script>"), + (({"fookey": "foovalue", "barkey": "barvalue"}, ), + "barkey = barvalue<br/>fookey = foovalue"), + (({"foo": "barquuz"}, 1, 2), "fo... = ba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 1, 3), "foo = bar..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 2, 3), + "foo = bar...<br/>zfo... = zba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 3, 3), + "foo = bar...<br/>zfo... = zba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 3, 8), + "foo = barquuz<br/>zfoo = zbarquuz"), + ) + @ddt.unpack + def test_metadata_to_str(self, input_args, expected_output): + result = utils.metadata_to_str(*input_args) + + self.assertEqual(expected_output, result)
fca19a1b0d42Fix metadata_to_str function code injection vulnerability
5 files changed · +46 −20
manila_ui/dashboards/admin/shares/tabs.py+3 −5 modified@@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -25,6 +24,7 @@ from manila_ui.api import network from manila_ui.dashboards.admin.shares import tables from manila_ui.dashboards.admin.shares import utils +from manila_ui.dashboards import utils as common_utils class SnapshotsTab(tabs.TableTab): @@ -104,10 +104,8 @@ def get_share_types_data(self): _("Unable to retrieve share types")) # Convert dict with extra specs to friendly view for st in share_types: - es_str = "" - for k, v in st.extra_specs.iteritems(): - es_str += "%s=%s\r\n<br />" % (k, v) - st.extra_specs = mark_safe(es_str) + st.extra_specs = common_utils.metadata_to_str( + st.extra_specs, 8, 45) return share_types
manila_ui/dashboards/project/shares/shares/tables.py+1 −3 modified@@ -15,7 +15,6 @@ from django.core.urlresolvers import NoReverseMatch # noqa from django.core.urlresolvers import reverse from django.template.defaultfilters import title # noqa -from django.utils.safestring import mark_safe from django.utils.translation import string_concat, ugettext_lazy # noqa from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ @@ -149,8 +148,7 @@ def get_data(self, request, share_id): share.share_network = share_net.name or share_net.id else: share.share_network = None - meta_str = utils.metadata_to_str(share.metadata) - share.metadata = mark_safe(meta_str) + share.metadata = utils.metadata_to_str(share.metadata) return share
manila_ui/dashboards/project/shares/shares/tabs.py+4 −6 modified@@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -42,11 +41,10 @@ def get_shares_data(self): try: shares = manila.share_list(self.request) for share in shares: - share.share_network = \ - share_nets_names.get(share.share_network_id) or \ - share.share_network_id - meta_str = utils.metadata_to_str(share.metadata) - share.metadata = mark_safe(meta_str) + share.share_network = ( + share_nets_names.get(share.share_network_id) or + share.share_network_id) + share.metadata = utils.metadata_to_str(share.metadata) snapshots = manila.share_snapshot_list(self.request, detailed=True) share_ids_with_snapshots = []
manila_ui/dashboards/utils.py+18 −6 modified@@ -13,9 +13,23 @@ # under the License. from django.forms import ValidationError # noqa +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +html_escape_table = { + "&": "&", + '"': """, + "'": "'", + ">": ">", + "<": "<", +} + + +def html_escape(text): + return ''.join(html_escape_table.get(s, s) for s in text) + + def parse_str_meta(meta_s): """Parse multiline string with data from form. @@ -58,14 +72,12 @@ def parse_str_meta(meta_s): return set_dict, unset_list -def metadata_to_str(metadata): +def metadata_to_str(metadata, meta_visible_limit=4, text_length_limit=25): # Only convert dictionaries if not hasattr(metadata, 'keys'): return metadata - meta_visible_limit = 4 - text_length_limit = 25 meta = [] meta_keys = metadata.keys() meta_keys.sort() @@ -77,11 +89,11 @@ def metadata_to_str(metadata): v = metadata[k] if len(v) > text_length_limit: v = v[:text_length_limit] + '...' - meta.append("%s = %s" % (k_shortenned, v)) + meta.append("%s = %s" % (html_escape(k_shortenned), html_escape(v))) meta_str = "<br/>".join(meta) - if len(metadata.keys()) > meta_visible_limit: + if len(metadata.keys()) > meta_visible_limit and meta_str[-3:] != "...": meta_str += '...' - return meta_str + return mark_safe(meta_str) def get_nice_security_service_type(security_service):
manila_ui/tests/dashboards/test_utils.py+20 −0 modified@@ -62,6 +62,26 @@ def test_parse_str_meta_success( def test_parse_str_meta_validation_error(self, input_data): self.assertRaises(ValidationError, utils.parse_str_meta, input_data) + @ddt.data( + (({"a": "<script>alert('A')/*", "b": "*/</script>"}, ), + "a = <script>alert('A')/*<br/>b = */</script>"), + (({"fookey": "foovalue", "barkey": "barvalue"}, ), + "barkey = barvalue<br/>fookey = foovalue"), + (({"foo": "barquuz"}, 1, 2), "fo... = ba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 1, 3), "foo = bar..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 2, 3), + "foo = bar...<br/>zfo... = zba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 3, 3), + "foo = bar...<br/>zfo... = zba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 3, 8), + "foo = barquuz<br/>zfoo = zbarquuz"), + ) + @ddt.unpack + def test_metadata_to_str(self, input_args, expected_output): + result = utils.metadata_to_str(*input_args) + + self.assertEqual(expected_output, result) + @ddt.data( ("ldap", "LDAP"), ("active_directory", "Active Directory"),
89593686ef18Fix metadata_to_str function code injection vulnerability
5 files changed · +46 −20
manila_ui/dashboards/admin/shares/tabs.py+3 −5 modified@@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -25,6 +24,7 @@ from manila_ui.api import network from manila_ui.dashboards.admin.shares import tables from manila_ui.dashboards.admin.shares import utils +from manila_ui.dashboards import utils as common_utils class SnapshotsTab(tabs.TableTab): @@ -104,10 +104,8 @@ def get_share_types_data(self): _("Unable to retrieve share types")) # Convert dict with extra specs to friendly view for st in share_types: - es_str = "" - for k, v in st.extra_specs.iteritems(): - es_str += "%s=%s\r\n<br />" % (k, v) - st.extra_specs = mark_safe(es_str) + st.extra_specs = common_utils.metadata_to_str( + st.extra_specs, 8, 45) return share_types
manila_ui/dashboards/project/shares/shares/tables.py+1 −3 modified@@ -17,7 +17,6 @@ from django.core.urlresolvers import NoReverseMatch # noqa from django.core.urlresolvers import reverse from django.template.defaultfilters import title # noqa -from django.utils.safestring import mark_safe from django.utils.translation import string_concat, ugettext_lazy # noqa from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ @@ -150,8 +149,7 @@ def get_data(self, request, share_id): share.share_network = share_net.name or share_net.id else: share.share_network = None - meta_str = utils.metadata_to_str(share.metadata) - share.metadata = mark_safe(meta_str) + share.metadata = utils.metadata_to_str(share.metadata) return share
manila_ui/dashboards/project/shares/shares/tabs.py+4 −6 modified@@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -42,11 +41,10 @@ def get_shares_data(self): try: shares = manila.share_list(self.request) for share in shares: - share.share_network = \ - share_nets_names.get(share.share_network_id) or \ - share.share_network_id - meta_str = utils.metadata_to_str(share.metadata) - share.metadata = mark_safe(meta_str) + share.share_network = ( + share_nets_names.get(share.share_network_id) or + share.share_network_id) + share.metadata = utils.metadata_to_str(share.metadata) snapshots = manila.share_snapshot_list(self.request, detailed=True) share_ids_with_snapshots = []
manila_ui/dashboards/utils.py+18 −6 modified@@ -13,9 +13,23 @@ # under the License. from django.forms import ValidationError # noqa +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ +html_escape_table = { + "&": "&", + '"': """, + "'": "'", + ">": ">", + "<": "<", +} + + +def html_escape(text): + return ''.join(html_escape_table.get(s, s) for s in text) + + def parse_str_meta(meta_s): """Parse multiline string with data from form. @@ -58,14 +72,12 @@ def parse_str_meta(meta_s): return set_dict, unset_list -def metadata_to_str(metadata): +def metadata_to_str(metadata, meta_visible_limit=4, text_length_limit=25): # Only convert dictionaries if not hasattr(metadata, 'keys'): return metadata - meta_visible_limit = 4 - text_length_limit = 25 meta = [] meta_keys = metadata.keys() meta_keys.sort() @@ -77,8 +89,8 @@ def metadata_to_str(metadata): v = metadata[k] if len(v) > text_length_limit: v = v[:text_length_limit] + '...' - meta.append("%s = %s" % (k_shortenned, v)) + meta.append("%s = %s" % (html_escape(k_shortenned), html_escape(v))) meta_str = "<br/>".join(meta) - if len(metadata.keys()) > meta_visible_limit: + if len(metadata.keys()) > meta_visible_limit and meta_str[-3:] != "...": meta_str += '...' - return meta_str + return mark_safe(meta_str)
manila_ui/test/dashboards/test_utils.py+20 −0 modified@@ -61,3 +61,23 @@ def test_parse_str_meta_success( ) def test_parse_str_meta_validation_error(self, input_data): self.assertRaises(ValidationError, utils.parse_str_meta, input_data) + + @ddt.data( + (({"a": "<script>alert('A')/*", "b": "*/</script>"}, ), + "a = <script>alert('A')/*<br/>b = */</script>"), + (({"fookey": "foovalue", "barkey": "barvalue"}, ), + "barkey = barvalue<br/>fookey = foovalue"), + (({"foo": "barquuz"}, 1, 2), "fo... = ba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 1, 3), "foo = bar..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 2, 3), + "foo = bar...<br/>zfo... = zba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 3, 3), + "foo = bar...<br/>zfo... = zba..."), + (({"foo": "barquuz", "zfoo": "zbarquuz"}, 3, 8), + "foo = barquuz<br/>zfoo = zbarquuz"), + ) + @ddt.unpack + def test_metadata_to_str(self, input_args, expected_output): + result = utils.metadata_to_str(*input_args) + + self.assertEqual(expected_output, result)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
12- github.com/advisories/GHSA-vq76-5ghr-9p4vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-6519ghsaADVISORY
- rhn.redhat.com/errata/RHSA-2016-2115.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2016-2116.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2016-2117.htmlnvdWEB
- www.openwall.com/lists/oss-security/2016/09/15/7nvdWEB
- www.securityfocus.com/bid/93001nvdWEB
- bugs.launchpad.net/manila-ui/+bug/1597738nvdWEB
- bugzilla.redhat.com/show_bug.cginvdWEB
- github.com/openstack/manila-ui/commit/009913d725bee34cef0bd62e47a298025ace2696ghsaWEB
- github.com/openstack/manila-ui/commit/89593686ef18f2bd06223b92071b4be2362a5abdghsaWEB
- github.com/openstack/manila-ui/commit/fca19a1b0d42536644212c5d673fbd6866e67c43ghsaWEB
News mentions
0No linked articles in our index yet.