Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a8b32fe

Browse files
apollo13adamchainz
authored andcommitted
[3.2.x] Fixed CVE-2021-45115 -- Prevented DoS vector in UserAttributeSimilarityValidator.
Thanks Chris Bailey for the report. Co-authored-by: Adam Johnson <[email protected]>
1 parent b0aa070 commit a8b32fe

File tree

5 files changed

+78
-15
lines changed

5 files changed

+78
-15
lines changed

django/contrib/auth/password_validation.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,36 @@ def get_help_text(self):
115115
) % {'min_length': self.min_length}
116116

117117

118+
def exceeds_maximum_length_ratio(password, max_similarity, value):
119+
"""
120+
Test that value is within a reasonable range of password.
121+
122+
The following ratio calculations are based on testing SequenceMatcher like
123+
this:
124+
125+
for i in range(0,6):
126+
print(10**i, SequenceMatcher(a='A', b='A'*(10**i)).quick_ratio())
127+
128+
which yields:
129+
130+
1 1.0
131+
10 0.18181818181818182
132+
100 0.019801980198019802
133+
1000 0.001998001998001998
134+
10000 0.00019998000199980003
135+
100000 1.999980000199998e-05
136+
137+
This means a length_ratio of 10 should never yield a similarity higher than
138+
0.2, for 100 this is down to 0.02 and for 1000 it is 0.002. This can be
139+
calculated via 2 / length_ratio. As a result we avoid the potentially
140+
expensive sequence matching.
141+
"""
142+
pwd_len = len(password)
143+
length_bound_similarity = max_similarity / 2 * pwd_len
144+
value_len = len(value)
145+
return pwd_len >= 10 * value_len and value_len < length_bound_similarity
146+
147+
118148
class UserAttributeSimilarityValidator:
119149
"""
120150
Validate whether the password is sufficiently different from the user's
@@ -130,19 +160,25 @@ class UserAttributeSimilarityValidator:
130160

131161
def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
132162
self.user_attributes = user_attributes
163+
if max_similarity < 0.1:
164+
raise ValueError('max_similarity must be at least 0.1')
133165
self.max_similarity = max_similarity
134166

135167
def validate(self, password, user=None):
136168
if not user:
137169
return
138170

171+
password = password.lower()
139172
for attribute_name in self.user_attributes:
140173
value = getattr(user, attribute_name, None)
141174
if not value or not isinstance(value, str):
142175
continue
143-
value_parts = re.split(r'\W+', value) + [value]
176+
value_lower = value.lower()
177+
value_parts = re.split(r'\W+', value_lower) + [value_lower]
144178
for value_part in value_parts:
145-
if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
179+
if exceeds_maximum_length_ratio(password, self.max_similarity, value_part):
180+
continue
181+
if SequenceMatcher(a=password, b=value_part).quick_ratio() >= self.max_similarity:
146182
try:
147183
verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
148184
except FieldDoesNotExist:

docs/releases/2.2.26.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,16 @@ Django 2.2.26 release notes
77
Django 2.2.26 fixes one security issue with severity "medium" and two security
88
issues with severity "low" in 2.2.25.
99

10-
...
10+
CVE-2021-45115: Denial-of-service possibility in ``UserAttributeSimilarityValidator``
11+
=====================================================================================
12+
13+
:class:`.UserAttributeSimilarityValidator` incurred significant overhead
14+
evaluating submitted password that were artificially large in relative to the
15+
comparison values. On the assumption that access to user registration was
16+
unrestricted this provided a potential vector for a denial-of-service attack.
17+
18+
In order to mitigate this issue, relatively long values are now ignored by
19+
``UserAttributeSimilarityValidator``.
20+
21+
This issue has severity "medium" according to the :ref:`Django security policy
22+
<security-disclosure>`.

docs/releases/3.2.11.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,16 @@ Django 3.2.11 release notes
77
Django 3.2.11 fixes one security issue with severity "medium" and two security
88
issues with severity "low" in 3.2.10.
99

10-
...
10+
CVE-2021-45115: Denial-of-service possibility in ``UserAttributeSimilarityValidator``
11+
=====================================================================================
12+
13+
:class:`.UserAttributeSimilarityValidator` incurred significant overhead
14+
evaluating submitted password that were artificially large in relative to the
15+
comparison values. On the assumption that access to user registration was
16+
unrestricted this provided a potential vector for a denial-of-service attack.
17+
18+
In order to mitigate this issue, relatively long values are now ignored by
19+
``UserAttributeSimilarityValidator``.
20+
21+
This issue has severity "medium" according to the :ref:`Django security policy
22+
<security-disclosure>`.

docs/topics/auth/passwords.txt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,16 @@ Django includes four validators:
539539
is used: ``'username', 'first_name', 'last_name', 'email'``.
540540
Attributes that don't exist are ignored.
541541

542-
The minimum similarity of a rejected password can be set on a scale of 0 to
543-
1 with the ``max_similarity`` parameter. A setting of 0 rejects all
544-
passwords, whereas a setting of 1 rejects only passwords that are identical
545-
to an attribute's value.
542+
The maximum allowed similarity of passwords can be set on a scale of 0.1
543+
to 1.0 with the ``max_similarity`` parameter. This is compared to the
544+
result of :meth:`difflib.SequenceMatcher.quick_ratio`. A value of 0.1
545+
rejects passwords unless they are substantially different from the
546+
``user_attributes``, whereas a value of 1.0 rejects only passwords that are
547+
identical to an attribute's value.
548+
549+
.. versionchanged:: 2.2.26
550+
551+
The ``max_similarity`` parameter was limited to a minimum value of 0.1.
546552

547553
.. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)
548554

tests/auth_tests/test_validators.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,10 @@ def test_validate(self):
150150
max_similarity=1,
151151
).validate(user.first_name, user=user)
152152
self.assertEqual(cm.exception.messages, [expected_error % "first name"])
153-
# max_similarity=0 rejects all passwords.
154-
with self.assertRaises(ValidationError) as cm:
155-
UserAttributeSimilarityValidator(
156-
user_attributes=['first_name'],
157-
max_similarity=0,
158-
).validate('XXX', user=user)
159-
self.assertEqual(cm.exception.messages, [expected_error % "first name"])
153+
# Very low max_similarity is rejected.
154+
msg = 'max_similarity must be at least 0.1'
155+
with self.assertRaisesMessage(ValueError, msg):
156+
UserAttributeSimilarityValidator(max_similarity=0.09)
160157
# Passes validation.
161158
self.assertIsNone(
162159
UserAttributeSimilarityValidator(user_attributes=['first_name']).validate('testclient', user=user)

0 commit comments

Comments
 (0)