CVE-2022-3438 (Python, published 2022-10-10T12:15Z)
Description: value: Open Redirect in GitHub repository ikus060/rdiffweb prior to 2.5.0a4.
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/ikus060/rdiffweb
Files changed:
README.md
rdiffweb/controller/page_admin_users.py
rdiffweb/controller/page_pref_general.py
rdiffweb/controller/tests/test_page_admin_users.py
rdiffweb/controller/tests/test_page_prefs_general.py
rdiffweb/core/model/_user.py
Commit message: Enforce validation on fullname, username and email for increase security #224


Vulnerable code:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        cfg = cherrypy.tree.apps[''].cfg
        self.password.validators += [
            validators.length(
                min=cfg.password_min_length,
                max=cfg.password_max_length,
                message=_('Password must have between %(min)d and %(max)d characters.'),
            )
        ]

import logging
import re

# Define the logger
_logger = logging.getLogger(__name__)

PATTERN_EMAIL = re.compile(r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$')

            Length(max=256, message=_("Invalid email.")),
            Regexp(PATTERN_EMAIL, message=_("Invalid email.")),
    def test_edit_fullname(self):
            body={'action': 'edit', 'username': self.USERNAME, 'fullname': 'My fullname'},
        # Then user is updated successfully
        self.assertInBody("User information modified successfully.")
        # Then database is updated
        obj = UserObject.query.filter(UserObject.username == self.USERNAME).first()
        self.assertEqual('My fullname', obj.fullname)

    def test_add_edit_delete_user_with_encoding(self):
        """
        Check creation of user with non-ascii char.
        """
        self._add_user("Éric", "éric@test.com", "pr3j5Dwi", "/home/", UserObject.USER_ROLE)
        self.assertInBody("User added successfully.")
        self.assertInBody("Éric")
        self.assertInBody("éric@test.com")
        # Update user
        self._edit_user("Éric", "eric.létourno@test.com", "écureuil", "/tmp/", UserObject.ADMIN_ROLE)
        self.assertInBody("User information modified successfully.")
        self.assertInBody("Éric")
        self.assertInBody("eric.létourno@test.com")
        self.assertNotInBody("/home/")
        self.assertInBody("/tmp/")

        self._delete_user("Éric")
        self.assertInBody("User account removed.")
        self.assertNotInBody("Éric")
    def test_change_fullname(self):
        self._set_profile_info("test@test.com", "My Fullname")
        self.assertInBody("Profile updated successfully.")
        # Then database is updated with fullname
        self.assertInBody("My Fullname")
        user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
        self.assertEqual("My Fullname", user.fullname)
    def test_change_email_with_invalid_email(self):
        self._set_profile_info("@test.com")
        self.assertStatus(200)
        self.assertInBody("Invalid email")

        self._set_profile_info("test.com")
        self.assertStatus(200)
        self.assertInBody("Invalid email")

        self._set_profile_info("test")
        self.assertStatus(200)
        self.assertInBody("Invalid email")

        self._set_profile_info("test@te_st.com")
        self.assertStatus(200)
        self.assertInBody("Invalid email")

        self._set_profile_info("test@test.com, test2@test.com")
        self.assertInBody("Invalid email")
        self.assertInBody("Invalid email")

Fixed code:
* Enforce validation on fullname, username and email
            validators.length(min=3, message=_('Username too short.')),
            validators.regexp(UserObject.PATTERN_USERNAME, message=_('Must not contain any special characters.')),
            validators.regexp(UserObject.PATTERN_FULLNAME, message=_('Must not contain any special characters.')),
            validators.regexp(UserObject.PATTERN_EMAIL, message=_('Must be a valid email address.')),
from rdiffweb.core.model import UserObject
            Regexp(UserObject.PATTERN_FULLNAME, message=_('Must not contain any special characters.')),
            Length(max=256, message=_("Email too long.")),
            Regexp(UserObject.PATTERN_EMAIL, message=_("Must be a valid email address.")),
from parameterized import parameterized
    @parameterized.expand(
        [
            # Invalid
            ('evil.com', False),
            ('http://test', False),
            ('email@test.test', False),
            ('/test/', False),
            # Valid
            ('My fullname', True),
            ('Test Test', True),
            ('Éric Terrien-Pascal', True),
            ("Tel'c", True),
        ]
    )
    def test_edit_fullname_with_special_character(self, new_fullname, expected_valid):
            body={'action': 'edit', 'username': self.USERNAME, 'fullname': new_fullname},
        if expected_valid:
            self.assertInBody("User information modified successfully.")
            self.assertNotInBody("Fullname: Must not contain any special characters.")
        else:
            self.assertNotInBody("User information modified successfully.")
            self.assertInBody("Fullname: Must not contain any special characters.")

    @parameterized.expand(
        [
            # Invalid
            ('http://username', False),
            ('username@test.test', False),
            ('/username/', False),
            # Valid
            ('username.com', True),
            ('admin_user', True),
            ('test.test', True),
            ('test-test', True),
        ]
    )
    def test_add_user_with_special_character(self, new_username, expected_valid):
        self._add_user(new_username, "eric@test.com", "pr3j5Dwi", "/home/", UserObject.USER_ROLE)
        self.assertStatus(200)
        if expected_valid:
            self.assertInBody("User added successfully.")
            self.assertNotInBody("Username: Must not contain any special characters.")
        else:
            self.assertNotInBody("User added successfully.")
            self.assertInBody("Username: Must not contain any special characters.")
from parameterized import parameterized
    @parameterized.expand(
        [
            # Invalid
            ('@test.com', False),
            ('test.com', False),
            ('test@te_st.com', False),
            ('test@test.com, test2@test.com', False),
            # Valid
            ('test', True),
            ('My Fullname', True),
        ]
    )
    def test_change_fullname(self, new_fullname, expected_valid):
        self._set_profile_info("test@test.com", new_fullname)
        if expected_valid:
            self.assertInBody("Profile updated successfully.")
            # Then database is updated with fullname
            self.assertInBody(new_fullname)
            user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
            self.assertEqual(new_fullname, user.fullname)
        else:
            self.assertNotInBody("Profile updated successfully.")
    @parameterized.expand(
        [
            # Invalid
            ('@test.com', False),
            ('test.com', False),
            ('test', False),
            ('test@te_st.com', False),
            ('test@test.com, test2@test.com', False),
            # Valid
            ('test@test.com', True),
        ]
    )
    def test_change_email_with_invalid_email(self, new_email, expected_valid):
        self._set_profile_info(new_email)
        if expected_valid:
            self.assertInBody("Profile updated successfully.")
            self.assertNotInBody("Must be a valid email address.")
        else:
            self.assertNotInBody("Profile updated successfully.")
            self.assertInBody("Must be a valid email address.")
        self.assertInBody("Email too long.")
    # Value for role.
    # Value for mfa field
    # Regex pattern to be used for validation.
    PATTERN_EMAIL = r"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$"
    PATTERN_FULLNAME = r"""[^!"#$%&()*+,./:;<=>?@[\]_{|}~]+$"""
    PATTERN_USERNAME = r"[a-zA-Z0-9_.\-]+$"