CVE-2022-3371: Allocation of Resources Without Limits or Throttling (CWE-770)
value: Allocation of Resources Without Limits or Throttling in GitHub repository ikus060/rdiffweb prior to 2.5.0a3.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2022-09-30T14:15Z
Repository: https://github.com/ikus060/rdiffweb
Language: Python
Files changed: rdiffweb/controller/page_admin_users.py, rdiffweb/controller/page_pref_general.py, rdiffweb/controller/page_pref_tokens.py, rdiffweb/controller/tests/test_page_admin_users.py, rdiffweb/controller/tests/test_page_prefs_general.py, rdiffweb/controller/tests/test_page_prefs_tokens.py
Commit date: 2022-09-28 16:08:09 -0400
Commit message: Limit user's fullname and Token name field length

Diff: diff --git a/rdiffweb/controller/page_admin_users.py b/rdiffweb/controller/page_admin_users.py
index 662059dcb..b6d4e72da 100644
--- a/rdiffweb/controller/page_admin_users.py
+++ b/rdiffweb/controller/page_admin_users.py
@@ -74,11 +74,17 @@ class UserForm(CherryForm):
         validators=[
             validators.data_required(),
             validators.length(max=256, message=_('Username too long.')),
         ],
     )
-    fullname = StringField(_('Fullname'), validators=[validators.optional()])
+    fullname = StringField(
+        _('Fullname'),
+        validators=[
+            validators.optional(),
+            validators.length(max=256, message=_('Fullname too long.')),
+        ],
+    )
     email = EmailField(
         _('Email'),
         validators=[
             validators.optional(),
             validators.length(max=256, message=_('Email too long.')),
diff --git a/rdiffweb/controller/page_pref_general.py b/rdiffweb/controller/page_pref_general.py
index 05f003d88..fee69500f 100644
--- a/rdiffweb/controller/page_pref_general.py
+++ b/rdiffweb/controller/page_pref_general.py
@@ -23,11 +23,11 @@ import logging
 import re
 
 import cherrypy
 from wtforms.fields import HiddenField, PasswordField, StringField, SubmitField
 from wtforms.fields.html5 import EmailField
-from wtforms.validators import DataRequired, EqualTo, InputRequired, Length, Regexp
+from wtforms.validators import DataRequired, EqualTo, InputRequired, Length, Optional, Regexp
 
 from rdiffweb.controller import Controller, flash
 from rdiffweb.controller.form import CherryForm
 from rdiffweb.tools.i18n import gettext_lazy as _
 
@@ -38,11 +38,17 @@ PATTERN_EMAIL = re.compile(r'[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$')
 
 
 class UserProfileForm(CherryForm):
     action = HiddenField(default='set_profile_info')
     username = StringField(_('Username'), render_kw={'readonly': True})
-    fullname = StringField(_('Fullname'))
+    fullname = StringField(
+        _('Fullname'),
+        validators=[
+            Optional(),
+            Length(max=256, message=_('Fullname too long.')),
+        ],
+    )
     email = EmailField(
         _('Email'),
         validators=[
             DataRequired(),
             Length(max=256, message=_("Invalid email.")),
diff --git a/rdiffweb/controller/page_pref_tokens.py b/rdiffweb/controller/page_pref_tokens.py
index a1931d228..f961587e4 100644
--- a/rdiffweb/controller/page_pref_tokens.py
+++ b/rdiffweb/controller/page_pref_tokens.py
@@ -18,11 +18,11 @@
 
 import logging
 
 import cherrypy
 from wtforms.fields import DateField, HiddenField, StringField, SubmitField
-from wtforms.validators import DataRequired, Optional
+from wtforms.validators import DataRequired, Length, Optional
 
 from rdiffweb.controller import Controller, flash
 from rdiffweb.controller.filter_authorization import is_maintainer
 from rdiffweb.controller.form import CherryForm
 from rdiffweb.core.model import Token
@@ -36,11 +36,14 @@ class TokenForm(CherryForm):
     name = StringField(
         _('Token name'),
         description=_(
             'Used only to identify the purpose of the token. For example, the application that uses the token.'
         ),
-        validators=[DataRequired()],
+        validators=[
+            DataRequired(),
+            Length(max=256, message=_('Token name too long')),
+        ],
     )
     expiration = DateField(
         _('Expiration date'),
         description=_(
             'Allows the creation of a temporary token by defining an expiration date. Leave empty to keep the token forever.'
diff --git a/rdiffweb/controller/tests/test_page_admin_users.py b/rdiffweb/controller/tests/test_page_admin_users.py
index 18edefa6f..c35b9c481 100644
--- a/rdiffweb/controller/tests/test_page_admin_users.py
+++ b/rdiffweb/controller/tests/test_page_admin_users.py
@@ -56,11 +56,11 @@ class AbstractAdminTest(rdiffweb.test.WebCase):
         self._quota[userobj.username] = value
 
     def _load_quota(self, userobj):
         return self._quota.get(userobj.username, 0)
 
-    def _add_user(self, username=None, email=None, password=None, user_root=None, role=None, mfa=None):
+    def _add_user(self, username=None, email=None, password=None, user_root=None, role=None, mfa=None, fullname=None):
         b = {}
         b['action'] = 'add'
         if username is not None:
             b['username'] = username
         if email is not None:
@@ -71,10 +71,12 @@ class AbstractAdminTest(rdiffweb.test.WebCase):
             b['user_root'] = user_root
         if role is not None:
             b['role'] = str(role)
         if mfa is not None:
             b['mfa'] = str(mfa)
+        if fullname is not None:
+            b['fullname'] = str(fullname)
         self.getPage("/admin/users/", method='POST', body=b)
 
     def _edit_user(
         self, username=None, email=None, password=None, user_root=None, role=None, disk_quota=None, mfa=None
     ):
@@ -273,10 +275,19 @@ class AbstractAdminTest(rdiffweb.test.WebCase):
         self._add_user("test2", "test@test,com", "password", user_root, UserObject.USER_ROLE)
         # Then an error is raised
         self.assertStatus(200)
         self.assertInBody("Root directory too long.")
 
+    def test_add_with_fullname_too_long(self):
+        # Given a too long user root
+        fullname = "fullname" * 50
+        # When trying to create the user
+        self._add_user("test2", "test@test,com", "password", "/tmp/", UserObject.USER_ROLE, fullname=fullname)
+        # Then an error is raised
+        self.assertStatus(200)
+        self.assertInBody("Fullname too long.")
+
     def test_delete_user_with_not_existing_username(self):
         """
         Verify failure to delete invalid username.
         """
         self._delete_user("test3")
diff --git a/rdiffweb/controller/tests/test_page_prefs_general.py b/rdiffweb/controller/tests/test_page_prefs_general.py
index 14885f789..d6e1b2fa8 100644
--- a/rdiffweb/controller/tests/test_page_prefs_general.py
+++ b/rdiffweb/controller/tests/test_page_prefs_general.py
@@ -107,10 +107,22 @@ class PagePrefGeneralTest(rdiffweb.test.WebCase):
         self.assertStatus(200)
         self.assertNotInBody("Profile updated successfully.")
         user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
         self.assertEqual("", user.fullname)
 
+    def test_change_fullname_too_long(self):
+        # Given an authenticated user
+        # When update the fullname
+        self._set_profile_info("test@test.com", "Fullname" * 50)
+        # Then page return with error message
+        self.assertStatus(200)
+        self.assertNotInBody("Profile updated successfully.")
+        self.assertInBody("Fullname too long.")
+        # Then database is not updated
+        user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
+        self.assertEqual("", user.fullname)
+
     def test_change_email(self):
         self._set_profile_info("test@test.com")
         self.assertStatus(200)
         self.assertInBody("Profile updated successfully.")
 
diff --git a/rdiffweb/controller/tests/test_page_prefs_tokens.py b/rdiffweb/controller/tests/test_page_prefs_tokens.py
index 48fc573ce..5f7e24b1f 100644
--- a/rdiffweb/controller/tests/test_page_prefs_tokens.py
+++ b/rdiffweb/controller/tests/test_page_prefs_tokens.py
@@ -76,10 +76,23 @@ class PagePrefTokensTest(rdiffweb.test.WebCase):
         # Then token name get displayed in the view
         self.assertInBody('Token name: This field is required.')
         # Then access token is not created
         self.assertEqual(0, Token.query.filter(Token.userid == userobj.userid, Token.name == 'test-token-name').count())
 
+    def test_add_access_token_with_name_too_long(self):
+        # Given an existing user
+        # When adding a new access token with name too long.
+        self.getPage(
+            "/prefs/tokens",
+            method='POST',
+            body={'action': 'add_access_token', 'name': 'token' * 52, 'expiration_time': ''},
+        )
+        # Then page return with error message
+        self.assertStatus(200)
+        # Then token name get displayed in the view
+        self.assertInBody('Token name too long')
+
     def test_delete_access_token(self):
         # Given an existing user with access_token
         userobj = UserObject.get_user(self.USERNAME)
         userobj.add_access_token('test-token-name')
         # When deleting access token

Vulnerable code:     fullname = StringField(_('Fullname'), validators=[validators.optional()])
from wtforms.validators import DataRequired, EqualTo, InputRequired, Length, Regexp
    fullname = StringField(_('Fullname'))
from wtforms.validators import DataRequired, Optional
        validators=[DataRequired()],
    def _add_user(self, username=None, email=None, password=None, user_root=None, role=None, mfa=None):
Fixed code:     fullname = StringField(
        _('Fullname'),
        validators=[
            validators.optional(),
            validators.length(max=256, message=_('Fullname too long.')),
        ],
    )
from wtforms.validators import DataRequired, EqualTo, InputRequired, Length, Optional, Regexp
    fullname = StringField(
        _('Fullname'),
        validators=[
            Optional(),
            Length(max=256, message=_('Fullname too long.')),
        ],
    )
from wtforms.validators import DataRequired, Length, Optional
        validators=[
            DataRequired(),
            Length(max=256, message=_('Token name too long')),
        ],
    def _add_user(self, username=None, email=None, password=None, user_root=None, role=None, mfa=None, fullname=None):
        if fullname is not None:
            b['fullname'] = str(fullname)
    def test_add_with_fullname_too_long(self):
        # Given a too long user root
        fullname = "fullname" * 50
        # When trying to create the user
        self._add_user("test2", "test@test,com", "password", "/tmp/", UserObject.USER_ROLE, fullname=fullname)
        # Then an error is raised
        self.assertStatus(200)
        self.assertInBody("Fullname too long.")

    def test_change_fullname_too_long(self):
        # Given an authenticated user
        # When update the fullname
        self._set_profile_info("test@test.com", "Fullname" * 50)
        # Then page return with error message
        self.assertStatus(200)
        self.assertNotInBody("Profile updated successfully.")
        self.assertInBody("Fullname too long.")
        # Then database is not updated
        user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
        self.assertEqual("", user.fullname)

    def test_add_access_token_with_name_too_long(self):
        # Given an existing user
        # When adding a new access token with name too long.
        self.getPage(
            "/prefs/tokens",
            method='POST',
            body={'action': 'add_access_token', 'name': 'token' * 52, 'expiration_time': ''},
        )
        # Then page return with error message
        self.assertStatus(200)
        # Then token name get displayed in the view
        self.assertInBody('Token name too long')