CVE-2021-43791 (Python, published 2021-12-02T01:15Z)
Description: value: Zulip is an open source group chat application that combines real-time chat with threaded conversations. In affected versions expiration dates on the confirmation objects associated with email invitations were not enforced properly in the new account registration flow. A confirmation link takes a user to the check_prereg_key_and_redirect endpoint, before getting redirected to POST to /accounts/register/. The problem was that validation was happening in the check_prereg_key_and_redirect part and not in /accounts/register/ - meaning that one could submit an expired confirmation key and be able to register. The issue is fixed in Zulip 4.8. There are no known workarounds and users are advised to upgrade as soon as possible.
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: CWE-613 \u2014 Insufficient Session Expiration
According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."
Repository: https://github.com/zulip/zulip
Files changed:
confirmation/models.py
zerver/tests/test_signup.py
zerver/views/registration.py
zproject/urls.py
Commit message: CVE-2021-43791: Validate confirmation keys in /accounts/register/ codepath.

A confirmation link takes a user to the check_prereg_key_and_redirect
endpoint, before getting redirected to POST to /accounts/register/. The
problem was that validation was happening in the check_prereg_key_and_redirect
part and not in /accounts/register/ - meaning that one could submit an
expired confirmation key and be able to register.

We fix this by moving validation into /accouts/register/.


Vulnerable code:
    Confirmation.USER_REGISTRATION: ConfirmationType("check_prereg_key_and_redirect"),
        "check_prereg_key_and_redirect", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
    Confirmation.REALM_CREATION: ConfirmationType("check_prereg_key_and_redirect"),
        self.assert_length(queries, 89)
        url = "/accounts/do_confirm/" + email_change_key
        result = self.client_get(url)
        target_url = "/" + url.split("/", 3)[3]
        result = self.client_get(target_url)
        self.assert_in_response("The registration link has expired or is not valid.", response)
from typing import Any, Dict, List, Optional
def check_prereg_key_and_redirect(
    confirmation = Confirmation.objects.filter(confirmation_key=confirmation_key).first()
    # confirm_preregistrationuser.html just extracts the confirmation_key
    # (and GET parameters) and redirects to /accounts/register, so that the
    # user can enter their information on a cleaner URL.
    return render(
        request,
        "confirmation/confirm_preregistrationuser.html",
        context={"key": confirmation_key, "full_name": full_name},
    )
    try:
        confirmation = Confirmation.objects.get(confirmation_key=key)
    except Confirmation.DoesNotExist:
        return render(request, "zerver/confirmation_link_expired_error.html", status=404)
    prereg_user = confirmation.content_object
    if prereg_user.status == confirmation_settings.STATUS_REVOKED:
        return render(request, "zerver/confirmation_link_expired_error.html", status=404)
    check_prereg_key_and_redirect,
        check_prereg_key_and_redirect,
        name="check_prereg_key_and_redirect",

Fixed code:
    Confirmation.USER_REGISTRATION: ConfirmationType("get_prereg_key_and_redirect"),
        "get_prereg_key_and_redirect", validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS
    Confirmation.REALM_CREATION: ConfirmationType("get_prereg_key_and_redirect"),
        self.assert_length(queries, 91)
        result = self.client_post("/accounts/register/", {"key": email_change_key})
        key = url.split("/")[-1]
        confirmation_link_path = "/" + url.split("/", 3)[3]
        # Both the confirmation link and submitting the key to the registration endpoint
        # directly will return the appropriate error.
        result = self.client_get(confirmation_link_path)
        self.assertEqual(result.status_code, 404)
        self.assert_in_response(
            "Whoops. The confirmation link has expired or been deactivated.", result
        )

        result = self.client_post("/accounts/register/", {"key": key})
        self.assert_in_response(
            "Whoops. We couldn't find your confirmation link in the system.", response
        )
from typing import Any, Dict, List, Optional, Union
def get_prereg_key_and_redirect(
    key_check_result = check_prereg_key(request, confirmation_key)
    if isinstance(key_check_result, HttpResponse):
        return key_check_result
    # confirm_preregistrationuser.html just extracts the confirmation_key
    # (and GET parameters) and redirects to /accounts/register, so that the
    # user can enter their information on a cleaner URL.
    return render(
        request,
        "confirmation/confirm_preregistrationuser.html",
        context={"key": confirmation_key, "full_name": full_name},
    )


def check_prereg_key(
    request: HttpRequest, confirmation_key: str
) -> Union[Confirmation, HttpResponse]:
    """
    Checks if the Confirmation key is valid, returning the Confirmation object in case of success
    and an appropriate error page otherwise.
    """
    try:
        confirmation: Optional[Confirmation] = Confirmation.objects.get(
            confirmation_key=confirmation_key
        )
    except Confirmation.DoesNotExist:
        confirmation = None

    return confirmation
    key_check_result = check_prereg_key(request, key)
    if isinstance(key_check_result, HttpResponse):
        return key_check_result
    prereg_user = key_check_result.content_object
    get_prereg_key_and_redirect,
        get_prereg_key_and_redirect,
        name="get_prereg_key_and_redirect",