CVE-2020-29565
Description
An issue was discovered in OpenStack Horizon before 15.3.2, 16.x before 16.2.1, 17.x and 18.x before 18.3.3, 18.4.x, and 18.5.x. There is a lack of validation of the "next" parameter, which would allow someone to supply a malicious URL in Horizon that can cause an automatic redirect to the provided malicious URL.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OpenStack Horizon before 15.3.2, 16.2.1, 18.3.3 lacks validation of the 'next' parameter, enabling open redirect in workflow forms.
What is the vulnerability
OpenStack Horizon's workflow forms contain an open redirect vulnerability due to insufficient validation of the "next" parameter. An attacker can supply a malicious URL as the value of this parameter, which the Horizon dashboard will automatically redirect users to. This flaw affects Horizon versions before 15.3.2, 16.x before 16.2.1, 17.x and 18.x before 18.3.3, and 18.4.x and 18.5.x (patched in 18.6.0) [1][2][4]. The issue was reported by Pritam Singh (Red Hat) and disclosed via OSSA-2020-008 on December 3, 2020 [2].
How it can be exploited
Exploitation requires no authentication and can be triggered by tricking a victim into clicking a crafted link containing the malicious "next" parameter. Since Horizon is a web-based dashboard, an attacker can embed such a link in phishing emails, forum posts, or other web pages. The lack of validation means Horizon will process the redirect without sanitizing the URL, leading the victim's browser to the attacker-controlled site. The attack surface is limited to users who interact with Horizon's workflow forms that accept the "next" parameter [1][2].
Impact
Successful exploitation results in an open redirect, which can be leveraged for phishing attacks, credential theft, or malware distribution. An attacker could redirect users to a malicious site that mimics the Horizon login page, tricking them into entering their credentials. This is a moderate-severity issue (CVSS not yet assessed by NVD as of publication) but poses a real risk to organizations using affected Horizon versions [1][2].
Mitigation
Patches were provided via OpenDev gerrit reviews for the Stein and Train branches [2]. Upgrading to Horizon 15.3.2, 16.2.1, 18.3.3, or 18.6.0 (for 18.4.x/18.5.x) fixes the issue. No workarounds were disclosed beyond updating. Horizon users should apply the relevant patches as soon as possible to prevent open redirect attacks [1][2][4].
AI Insight generated on May 21, 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 |
|---|---|---|
horizonPyPI | < 15.3.2 | 15.3.2 |
horizonPyPI | >= 16.0.0, < 16.2.1 | 16.2.1 |
horizonPyPI | >= 17.0.0, < 18.3.3 | 18.3.3 |
horizonPyPI | >= 18.4.0, < 18.6.0 | 18.6.0 |
Affected products
8- OpenStack/Horizondescription
- ghsa-coords7 versionspkg:pypi/horizonpkg:rpm/suse/openstack-dashboard&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/openstack-dashboard&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/openstack-dashboard&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/release-notes-hpe-helion-openstack&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/release-notes-suse-openstack-cloud&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/release-notes-suse-openstack-cloud&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208
< 15.3.2+ 6 more
- (no CPE)range: < 15.3.2
- (no CPE)range: < 12.0.5~dev6-3.29.1
- (no CPE)range: < 12.0.5~dev6-3.29.1
- (no CPE)range: < 12.0.5~dev6-3.29.1
- (no CPE)range: < 8.20201214-3.26.1
- (no CPE)range: < 8.20201214-3.26.1
- (no CPE)range: < 8.20201214-3.26.1
Patches
4252467100f75Fix open redirect
3 files changed · +42 −2
horizon/test/unit/workflows/test_workflows.py+25 −0 modified@@ -16,6 +16,7 @@ from django import forms from django import http +from django.test.utils import override_settings from horizon import base from horizon import exceptions @@ -399,3 +400,27 @@ def test_entry_point(self): flow = WorkflowForTesting(req, entry_point="action_two") self.assertEqual("action_two", flow.get_entry_point()) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_safe(self): + url = 'http://localhost/test' + view = WorkflowViewForTesting() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertEqual(url, context['REDIRECT_URL']) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_unsafe(self): + url = 'http://evilcorp/test' + view = WorkflowViewForTesting() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertIsNone(context['REDIRECT_URL'])
horizon/workflows/views.py+10 −2 modified@@ -18,6 +18,7 @@ from django import forms from django import http from django import shortcuts +from django.utils import http as utils_http from django.views import generic from horizon import exceptions @@ -90,8 +91,15 @@ def get_context_data(self, **kwargs): workflow = self.get_workflow() workflow.verify_integrity() context[self.context_object_name] = workflow - next = self.request.GET.get(workflow.redirect_param_name) - context['REDIRECT_URL'] = next + + redirect_to = self.request.GET.get(workflow.redirect_param_name) + # Make sure the requested redirect is safe + if redirect_to and not utils_http.is_safe_url( + url=redirect_to, + allowed_hosts=[self.request.get_host()]): + redirect_to = None + context['REDIRECT_URL'] = redirect_to + context['layout'] = self.get_layout() # For consistency with Workflow class context['modal'] = 'modal' in context['layout']
releasenotes/notes/bug-cd9099c1ba78d637.yaml+7 −0 added@@ -0,0 +1,7 @@ +--- +security: + - | + An open redirect has been fixed, that could redirect users to arbitrary + addresses from certain views by specifying a "next" parameter in the URL. + Now the redirect will only work if the target URL is in the same domain, + and uses the same protocol.
6c208edf323cFix open redirect
3 files changed · +42 −3
horizon/test/unit/workflows/test_workflows.py+25 −1 modified@@ -14,8 +14,8 @@ from django import forms from django import http +from django.test.utils import override_settings import mock - import six from horizon import base @@ -401,3 +401,27 @@ def test_entry_point(self): flow = TestWorkflow(req, entry_point="test_action_two") self.assertEqual("test_action_two", flow.get_entry_point()) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_safe(self): + url = 'http://localhost/test' + view = TestWorkflowView() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertEqual(url, context['REDIRECT_URL']) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_unsafe(self): + url = 'http://evilcorp/test' + view = TestWorkflowView() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertIsNone(context['REDIRECT_URL'])
horizon/workflows/views.py+10 −2 modified@@ -18,6 +18,7 @@ from django import forms from django import http from django import shortcuts +from django.utils import http as utils_http from django.views import generic import six @@ -92,8 +93,15 @@ def get_context_data(self, **kwargs): workflow = self.get_workflow() workflow.verify_integrity() context[self.context_object_name] = workflow - next = self.request.GET.get(workflow.redirect_param_name) - context['REDIRECT_URL'] = next + + redirect_to = self.request.GET.get(workflow.redirect_param_name) + # Make sure the requested redirect is safe + if redirect_to and not utils_http.is_safe_url( + url=redirect_to, + allowed_hosts=[self.request.get_host()]): + redirect_to = None + context['REDIRECT_URL'] = redirect_to + context['layout'] = self.get_layout() # For consistency with Workflow class context['modal'] = 'modal' in context['layout']
releasenotes/notes/bug-cd9099c1ba78d637.yaml+7 −0 added@@ -0,0 +1,7 @@ +--- +security: + - | + An open redirect has been fixed, that could redirect users to arbitrary + addresses from certain views by specifying a "next" parameter in the URL. + Now the redirect will only work if the target URL is in the same domain, + and uses the same protocol.
9e0e333ab527Fix open redirect
3 files changed · +42 −3
horizon/test/unit/workflows/test_workflows.py+25 −1 modified@@ -14,8 +14,8 @@ from django import forms from django import http +from django.test.utils import override_settings import mock - import six from horizon import base @@ -401,3 +401,27 @@ def test_entry_point(self): flow = TestWorkflow(req, entry_point="test_action_two") self.assertEqual("test_action_two", flow.get_entry_point()) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_safe(self): + url = 'http://localhost/test' + view = TestWorkflowView() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertEqual(url, context['REDIRECT_URL']) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_unsafe(self): + url = 'http://evilcorp/test' + view = TestWorkflowView() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertIsNone(context['REDIRECT_URL'])
horizon/workflows/views.py+10 −2 modified@@ -18,6 +18,7 @@ from django import forms from django import http from django import shortcuts +from django.utils import http as utils_http from django.views import generic import six @@ -92,8 +93,15 @@ def get_context_data(self, **kwargs): workflow = self.get_workflow() workflow.verify_integrity() context[self.context_object_name] = workflow - next = self.request.GET.get(workflow.redirect_param_name) - context['REDIRECT_URL'] = next + + redirect_to = self.request.GET.get(workflow.redirect_param_name) + # Make sure the requested redirect is safe + if redirect_to and not utils_http.is_safe_url( + url=redirect_to, + allowed_hosts=[self.request.get_host()]): + redirect_to = None + context['REDIRECT_URL'] = redirect_to + context['layout'] = self.get_layout() # For consistency with Workflow class context['modal'] = 'modal' in context['layout']
releasenotes/notes/bug-cd9099c1ba78d637.yaml+7 −0 added@@ -0,0 +1,7 @@ +--- +security: + - | + An open redirect has been fixed, that could redirect users to arbitrary + addresses from certain views by specifying a "next" parameter in the URL. + Now the redirect will only work if the target URL is in the same domain, + and uses the same protocol.
baa370f84332Fix open redirect
3 files changed · +42 −2
horizon/test/unit/workflows/test_workflows.py+25 −0 modified@@ -16,6 +16,7 @@ from django import forms from django import http +from django.test.utils import override_settings from horizon import base from horizon import exceptions @@ -399,3 +400,27 @@ def test_entry_point(self): flow = TestWorkflow(req, entry_point="test_action_two") self.assertEqual("test_action_two", flow.get_entry_point()) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_safe(self): + url = 'http://localhost/test' + view = TestWorkflowView() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertEqual(url, context['REDIRECT_URL']) + + @override_settings(ALLOWED_HOSTS=['localhost']) + def test_redirect_url_unsafe(self): + url = 'http://evilcorp/test' + view = TestWorkflowView() + request = self.factory.get("/", data={ + 'next': url, + }) + request.META['SERVER_NAME'] = "localhost" + view.request = request + context = view.get_context_data() + self.assertIsNone(context['REDIRECT_URL'])
horizon/workflows/views.py+10 −2 modified@@ -18,6 +18,7 @@ from django import forms from django import http from django import shortcuts +from django.utils import http as utils_http from django.views import generic from horizon import exceptions @@ -90,8 +91,15 @@ def get_context_data(self, **kwargs): workflow = self.get_workflow() workflow.verify_integrity() context[self.context_object_name] = workflow - next = self.request.GET.get(workflow.redirect_param_name) - context['REDIRECT_URL'] = next + + redirect_to = self.request.GET.get(workflow.redirect_param_name) + # Make sure the requested redirect is safe + if redirect_to and not utils_http.is_safe_url( + url=redirect_to, + allowed_hosts=[self.request.get_host()]): + redirect_to = None + context['REDIRECT_URL'] = redirect_to + context['layout'] = self.get_layout() # For consistency with Workflow class context['modal'] = 'modal' in context['layout']
releasenotes/notes/bug-cd9099c1ba78d637.yaml+7 −0 added@@ -0,0 +1,7 @@ +--- +security: + - | + An open redirect has been fixed, that could redirect users to arbitrary + addresses from certain views by specifying a "next" parameter in the URL. + Now the redirect will only work if the target URL is in the same domain, + and uses the same protocol.
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- github.com/advisories/GHSA-f8fh-xp28-q59mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-29565ghsaADVISORY
- www.debian.org/security/2020/dsa-4820ghsavendor-advisoryx_refsource_DEBIANWEB
- www.openwall.com/lists/oss-security/2020/12/08/2ghsamailing-listx_refsource_MLISTWEB
- bugs.launchpad.net/horizon/+bug/1865026ghsax_refsource_MISCWEB
- github.com/openstack/horizon/commit/252467100f75587e18df9c43ed5802ee8f0017faghsaWEB
- github.com/openstack/horizon/commit/6c208edf323ced07b15ec4bc3879bddb91d398bcghsaWEB
- github.com/openstack/horizon/commit/9e0e333ab5277b6c396f602862ff90398cb0242bghsaWEB
- github.com/openstack/horizon/commit/baa370f84332ad41502daea29a551705696f4421ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/horizon/PYSEC-2020-45.yamlghsaWEB
- review.opendev.org/c/openstack/horizon/+/758841ghsaWEB
- review.opendev.org/c/openstack/horizon/+/758841/mitrex_refsource_MISC
- review.opendev.org/c/openstack/horizon/+/758843ghsaWEB
- review.opendev.org/c/openstack/horizon/+/758843/mitrex_refsource_MISC
- security.openstack.org/ossa/OSSA-2020-008.htmlghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.