VYPR
Moderate severityNVD Advisory· Published Dec 17, 2022· Updated Aug 3, 2024

cyface Terms and Conditions Module views.py returnTo redirect

CVE-2022-4589

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.

PackageAffected versionsPatched versions
django-termsandconditionsPyPI
< 2.0.112.0.11

Affected products

3

Patches

1
03396a1c2e0a

Fix open redirect vulnerability

https://github.com/cyface/django-termsandconditionsAmar SahinovicJun 20, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.