/[packages]/updates/8/python-django/current/SOURCES/a8b32fe13bcaed1c0b772fdc53de84abc224fb20.patch
ViewVC logotype

Contents of /updates/8/python-django/current/SOURCES/a8b32fe13bcaed1c0b772fdc53de84abc224fb20.patch

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1766876 - (show annotations) (download)
Tue Jan 4 20:20:05 2022 UTC (16 months, 3 weeks ago) by neoclust
File size: 5996 byte(s)
Add P100->102: Fixes CVE-2021-4511[56] and CVE-2021-45452 (mga#29843)
1 From a8b32fe13bcaed1c0b772fdc53de84abc224fb20 Mon Sep 17 00:00:00 2001
2 From: Florian Apolloner <florian@apolloner.eu>
3 Date: Mon, 27 Dec 2021 14:48:03 +0100
4 Subject: [PATCH] [3.2.x] Fixed CVE-2021-45115 -- Prevented DoS vector in
5 UserAttributeSimilarityValidator.
6
7 Thanks Chris Bailey for the report.
8
9 Co-authored-by: Adam Johnson <me@adamj.eu>
10 ---
11 django/contrib/auth/password_validation.py | 40 ++++++++++++++++++++--
12 docs/releases/2.2.26.txt | 14 +++++++-
13 docs/releases/3.2.11.txt | 14 +++++++-
14 docs/topics/auth/passwords.txt | 14 +++++---
15 tests/auth_tests/test_validators.py | 11 +++---
16 5 files changed, 78 insertions(+), 15 deletions(-)
17
18 diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py
19 index 845f4d86d5b2..7beb4bdc0ff2 100644
20 --- a/django/contrib/auth/password_validation.py
21 +++ b/django/contrib/auth/password_validation.py
22 @@ -115,6 +115,36 @@ def get_help_text(self):
23 ) % {'min_length': self.min_length}
24
25
26 +def exceeds_maximum_length_ratio(password, max_similarity, value):
27 + """
28 + Test that value is within a reasonable range of password.
29 +
30 + The following ratio calculations are based on testing SequenceMatcher like
31 + this:
32 +
33 + for i in range(0,6):
34 + print(10**i, SequenceMatcher(a='A', b='A'*(10**i)).quick_ratio())
35 +
36 + which yields:
37 +
38 + 1 1.0
39 + 10 0.18181818181818182
40 + 100 0.019801980198019802
41 + 1000 0.001998001998001998
42 + 10000 0.00019998000199980003
43 + 100000 1.999980000199998e-05
44 +
45 + This means a length_ratio of 10 should never yield a similarity higher than
46 + 0.2, for 100 this is down to 0.02 and for 1000 it is 0.002. This can be
47 + calculated via 2 / length_ratio. As a result we avoid the potentially
48 + expensive sequence matching.
49 + """
50 + pwd_len = len(password)
51 + length_bound_similarity = max_similarity / 2 * pwd_len
52 + value_len = len(value)
53 + return pwd_len >= 10 * value_len and value_len < length_bound_similarity
54 +
55 +
56 class UserAttributeSimilarityValidator:
57 """
58 Validate whether the password is sufficiently different from the user's
59 @@ -130,19 +160,25 @@ class UserAttributeSimilarityValidator:
60
61 def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
62 self.user_attributes = user_attributes
63 + if max_similarity < 0.1:
64 + raise ValueError('max_similarity must be at least 0.1')
65 self.max_similarity = max_similarity
66
67 def validate(self, password, user=None):
68 if not user:
69 return
70
71 + password = password.lower()
72 for attribute_name in self.user_attributes:
73 value = getattr(user, attribute_name, None)
74 if not value or not isinstance(value, str):
75 continue
76 - value_parts = re.split(r'\W+', value) + [value]
77 + value_lower = value.lower()
78 + value_parts = re.split(r'\W+', value_lower) + [value_lower]
79 for value_part in value_parts:
80 - if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
81 + if exceeds_maximum_length_ratio(password, self.max_similarity, value_part):
82 + continue
83 + if SequenceMatcher(a=password, b=value_part).quick_ratio() >= self.max_similarity:
84 try:
85 verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
86 except FieldDoesNotExist:
87 diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt
88 index 52c90d574b42..8fc4ba6ed41a 100644
89 --- a/docs/topics/auth/passwords.txt
90 +++ b/docs/topics/auth/passwords.txt
91 @@ -539,10 +539,16 @@ Django includes four validators:
92 is used: ``'username', 'first_name', 'last_name', 'email'``.
93 Attributes that don't exist are ignored.
94
95 - The minimum similarity of a rejected password can be set on a scale of 0 to
96 - 1 with the ``max_similarity`` parameter. A setting of 0 rejects all
97 - passwords, whereas a setting of 1 rejects only passwords that are identical
98 - to an attribute's value.
99 + The maximum allowed similarity of passwords can be set on a scale of 0.1
100 + to 1.0 with the ``max_similarity`` parameter. This is compared to the
101 + result of :meth:`difflib.SequenceMatcher.quick_ratio`. A value of 0.1
102 + rejects passwords unless they are substantially different from the
103 + ``user_attributes``, whereas a value of 1.0 rejects only passwords that are
104 + identical to an attribute's value.
105 +
106 + .. versionchanged:: 2.2.26
107 +
108 + The ``max_similarity`` parameter was limited to a minimum value of 0.1.
109
110 .. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)
111
112 diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py
113 index 393fbdd39c8f..f4aaf3305290 100644
114 --- a/tests/auth_tests/test_validators.py
115 +++ b/tests/auth_tests/test_validators.py
116 @@ -150,13 +150,10 @@ def test_validate(self):
117 max_similarity=1,
118 ).validate(user.first_name, user=user)
119 self.assertEqual(cm.exception.messages, [expected_error % "first name"])
120 - # max_similarity=0 rejects all passwords.
121 - with self.assertRaises(ValidationError) as cm:
122 - UserAttributeSimilarityValidator(
123 - user_attributes=['first_name'],
124 - max_similarity=0,
125 - ).validate('XXX', user=user)
126 - self.assertEqual(cm.exception.messages, [expected_error % "first name"])
127 + # Very low max_similarity is rejected.
128 + msg = 'max_similarity must be at least 0.1'
129 + with self.assertRaisesMessage(ValueError, msg):
130 + UserAttributeSimilarityValidator(max_similarity=0.09)
131 # Passes validation.
132 self.assertIsNone(
133 UserAttributeSimilarityValidator(user_attributes=['first_name']).validate('testclient', user=user)

  ViewVC Help
Powered by ViewVC 1.1.28