VYPR
Moderate severityNVD Advisory· Published Dec 4, 2020· Updated Aug 4, 2024

CVE-2020-29565

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.

PackageAffected versionsPatched versions
horizonPyPI
< 15.3.215.3.2
horizonPyPI
>= 16.0.0, < 16.2.116.2.1
horizonPyPI
>= 17.0.0, < 18.3.318.3.3
horizonPyPI
>= 18.4.0, < 18.6.018.6.0

Affected products

8

Patches

4
252467100f75

Fix open redirect

https://github.com/openstack/horizonRadomir DopieralskiSep 7, 2020via ghsa
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.
    
6c208edf323c

Fix open redirect

https://github.com/openstack/horizonRadomir DopieralskiSep 7, 2020via ghsa
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.
    
9e0e333ab527

Fix open redirect

https://github.com/openstack/horizonRadomir DopieralskiSep 7, 2020via ghsa
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.
    
baa370f84332

Fix open redirect

https://github.com/openstack/horizonRadomir DopieralskiSep 7, 2020via ghsa
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

News mentions

0

No linked articles in our index yet.