cyface Terms and Conditions Module views.py returnTo redirect
Description
A vulnerability has been found in cyface Terms and Conditions Module up to 2.0.9 and classified as problematic. Affected by this vulnerability is the function returnTo of the file termsandconditions/views.py. The manipulation leads to open redirect. The attack can be launched remotely. Upgrading to version 2.0.10 is able to address this issue. The name of the patch is 03396a1c2e0af95e12a45c5faef7e47a4b513e1a. It is recommended to upgrade the affected component. The associated identifier of this vulnerability is VDB-216175.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An open redirect vulnerability exists in cyface Terms and Conditions Module up to version 2.0.9 via the returnTo parameter.
Vulnerability
Description
CVE-2022-4589 is an open redirect vulnerability in the cyface django-termsandconditions module (versions up to 2.0.9). The flaw resides in the returnTo function within the termsandconditions/views.py file. The application fails to properly validate user-supplied URLs, allowing an attacker to craft a malicious link that redirects users to an arbitrary external domain [1][2].
Exploitation
An attacker can exploit this remotely by sending a crafted request containing a manipulated returnTo parameter (either via GET or POST) to the vulnerable endpoint. The impact is limited to open redirect; no authentication is required. The vulnerability is classified as problematic with a low severity [2].
Impact
Successful exploitation allows an attacker to redirect users to a malicious site, potentially enabling phishing attacks or other social engineering schemes. The redirect can be used to bypass URL validation and trick users into trusting a malicious destination [1][2].
Mitigation
The issue is patched in version 2.0.10 [3]. The fix, commit 03396a1c2e0af95e12a45c5faef7e47a4b513e1a, implements proper URL validation using Django's is_safe_url (or url_has_allowed_host_and_scheme) method, which checks the URL against the ALLOWED_HOSTS setting before allowing the redirect [4]. Users are strongly advised to upgrade to the latest version.
AI Insight generated on May 20, 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 |
|---|---|---|
django-termsandconditionsPyPI | < 2.0.11 | 2.0.11 |
Affected products
3- Range: <=2.0.9
- cyface/Terms and Conditions Modulev5Range: 2.0.0
Patches
103396a1c2e0aFix open redirect vulnerability
2 files changed · +45 −18
termsandconditions/tests.py+18 −9 modified@@ -242,7 +242,7 @@ def test_accept(self): ) self.assertContains(accept_version_post_response, "Secure") - def test_accept_redirect_safe(self): + def _post_accept(self, return_to): # Pre-accept terms 2 and 3 UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms2) UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms3) @@ -253,19 +253,28 @@ def test_accept_redirect_safe(self): LOGGER.debug("Test /terms/accept/site-terms/1/ post") accept_response = self.client.post( - "/terms/accept/", {"terms": 1, "returnTo": "/secure/"}, follow=True + "/terms/accept/", {"terms": 1, "returnTo": return_to}, follow=True ) + return accept_response + + def test_accept_redirect_safe(self): + accept_response = self._post_accept("/secure/") self.assertRedirects(accept_response, "/secure/") def test_accept_redirect_unsafe(self): - # Pre-accept terms 2 and 3 - UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms2) - UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms3) + accept_response = self._post_accept("http://attacker/") + self.assertRedirects(accept_response, "/") - LOGGER.debug("Test /terms/accept/contrib-terms/3/ post") - accept_response = self.client.post( - "/terms/accept/", {"terms": 3, "returnTo": "http://attacker/"}, follow=False - ) + def test_accept_redirect_unsafe_2(self): + accept_response = self._post_accept("//attacker.com") + self.assertRedirects(accept_response, "/") + + def test_accept_redirect_unsafe_3(self): + accept_response = self._post_accept("///attacker.com") + self.assertRedirects(accept_response, "/") + + def test_accept_redirect_unsafe_4(self): + accept_response = self._post_accept("////attacker.com") self.assertRedirects(accept_response, "/") def test_accept_store_ip_address(self):
termsandconditions/views.py+27 −9 modified@@ -14,6 +14,7 @@ from django.utils.translation import gettext as _ from django.views.generic import DetailView, CreateView, FormView from django.template.loader import get_template +from django.utils.encoding import iri_to_uri import logging from smtplib import SMTPException @@ -44,6 +45,28 @@ def get_terms(self, kwargs): terms = TermsAndConditions.get_active_terms_not_agreed_to(self.request.user) return terms + def get_return_to(self, from_dict): + return_to = from_dict.get("returnTo", "/") + + if self.is_safe_url(return_to): + # Django recommends to use this together with the helper above + return iri_to_uri(return_to) + + LOGGER.debug("Unsafe URL found: {}".format(return_to)) + return "/" + + def is_safe_url(self, url): + # In Django 3.0 is_safe_url is renamed, so we import conditionally: + # https://docs.djangoproject.com/en/3.2/releases/3.0/#id3 + try: + from django.utils.http import url_has_allowed_host_and_scheme + except ImportError: + from django.utils.http import ( + is_safe_url as url_has_allowed_host_and_scheme, + ) + + return url_has_allowed_host_and_scheme(url, settings.ALLOWED_HOSTS) + class AcceptTermsView(CreateView, GetTermsViewMixin): """ @@ -69,22 +92,17 @@ def get_initial(self): LOGGER.debug("termsandconditions.views.AcceptTermsView.get_initial") terms = self.get_terms(self.kwargs) - return_to = self.request.GET.get("returnTo", "/") + return_to = self.get_return_to(self.request.GET) return {"terms": terms, "returnTo": return_to} def post(self, request, *args, **kwargs): """ Handles POST request. """ - return_url = request.POST.get("returnTo", "/") + return_url = self.get_return_to(self.request.POST) terms_ids = request.POST.getlist("terms") - parsed = urlparse(return_url) - if parsed.hostname and parsed.hostname not in settings.ALLOWED_HOSTS: - # Make sure the return url is a relative path or a trusted hostname - return_url = '/' - if not terms_ids: # pragma: nocover return HttpResponseRedirect(return_url) @@ -147,7 +165,7 @@ def get_initial(self): terms = self.get_terms(self.kwargs) - return_to = self.request.GET.get("returnTo", "/") + return_to = self.get_return_to(self.request.GET) return {"terms": terms, "returnTo": return_to} @@ -179,7 +197,7 @@ def form_valid(self, form): _("An Error Occurred Sending Your Message."), ) - self.success_url = form.cleaned_data.get("returnTo", "/") or "/" + self.success_url = self.get_return_to(form.cleaned_data) return super().form_valid(form)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/cyface/django-termsandconditions/commit/03396a1c2e0af95e12a45c5faef7e47a4b513e1aghsamitigationpatchWEB
- github.com/advisories/GHSA-6rmf-cv6p-4h27ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-4589ghsaADVISORY
- github.com/cyface/django-termsandconditions/pull/239ghsarelatedWEB
- github.com/cyface/django-termsandconditions/releases/tag/v2.0.10ghsamitigationWEB
- github.com/cyface/django-termsandconditions/releases/tag/v2.0.11ghsaWEB
- vuldb.comghsatechnical-descriptionvdb-entryWEB
News mentions
0No linked articles in our index yet.