CVE-2022-4589 (Python, published 2022-12-17T13:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 6.1)
Weakness: CWE-601 \u2014 URL Redirection to Untrusted Site ('Open Redirect')
A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
Repository: https://github.com/cyface/django-termsandconditions
Files changed:
termsandconditions/tests.py
termsandconditions/views.py
Commit message: Fix open redirect vulnerability


Vulnerable code:
    def test_accept_redirect_safe(self):
            "/terms/accept/", {"terms": 1, "returnTo": "/secure/"}, follow=True
        # Pre-accept terms 2 and 3
        UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms2)
        UserTermsAndConditions.objects.create(user=self.user1, terms=self.terms3)
        LOGGER.debug("Test /terms/accept/contrib-terms/3/ post")
        accept_response = self.client.post(
            "/terms/accept/", {"terms": 3, "returnTo": "http://attacker/"}, follow=False
        )
        return_to = self.request.GET.get("returnTo", "/")
        return_url = request.POST.get("returnTo", "/")
        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 = '/'

        return_to = self.request.GET.get("returnTo", "/")
        self.success_url = form.cleaned_data.get("returnTo", "/") or "/"

Fixed code:
    def _post_accept(self, return_to):
            "/terms/accept/", {"terms": 1, "returnTo": return_to}, follow=True
        return accept_response

    def test_accept_redirect_safe(self):
        accept_response = self._post_accept("/secure/")
        accept_response = self._post_accept("http://attacker/")
        self.assertRedirects(accept_response, "/")
    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")
from django.utils.encoding import iri_to_uri
    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)

        return_to = self.get_return_to(self.request.GET)
        return_url = self.get_return_to(self.request.POST)
        return_to = self.get_return_to(self.request.GET)
        self.success_url = self.get_return_to(form.cleaned_data)