티스토리 수익 글 보기

티스토리 수익 글 보기

[1.7.x] Fixed DoS possibility in ModelMultipleChoiceField. · django/django@bcfb477 · GitHub
Skip to content

Commit bcfb477

Browse files
committed
[1.7.x] Fixed DoS possibility in ModelMultipleChoiceField.
This is a security fix. Disclosure following shortly. Thanks Keryn Knight for the report and initial patch.
1 parent 818e59a commit bcfb477

File tree

5 files changed

+63
5
lines changed

5 files changed

+63
5
lines changed

django/forms/models.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,8 +1221,7 @@ def __init__(self, queryset, cache_choices=False, required=True,
12211221
def to_python(self, value):
12221222
if not value:
12231223
return []
1224-
to_py = super(ModelMultipleChoiceField, self).to_python
1225-
return [to_py(val) for val in value]
1224+
return list(self._check_values(value))
12261225

12271226
def clean(self, value):
12281227
if self.required and not value:
@@ -1231,7 +1230,29 @@ def clean(self, value):
12311230
return self.queryset.none()
12321231
if not isinstance(value, (list, tuple)):
12331232
raise ValidationError(self.error_messages['list'], code='list')
1233+
qs = self._check_values(value)
1234+
# Since this overrides the inherited ModelChoiceField.clean
1235+
# we run custom validators here
1236+
self.run_validators(value)
1237+
return qs
1238+
1239+
def _check_values(self, value):
1240+
"""
1241+
Given a list of possible PK values, returns a QuerySet of the
1242+
corresponding objects. Raises a ValidationError if a given value is
1243+
invalid (not a valid PK, not in the queryset, etc.)
1244+
"""
12341245
key = self.to_field_name or 'pk'
1246+
# deduplicate given values to avoid creating many querysets or
1247+
# requiring the database backend deduplicate efficiently.
1248+
try:
1249+
value = frozenset(value)
1250+
except TypeError:
1251+
# list of lists isn't hashable, for example
1252+
raise ValidationError(
1253+
self.error_messages['list'],
1254+
code='list',
1255+
)
12351256
for pk in value:
12361257
try:
12371258
self.queryset.filter(**{key: pk})
@@ -1250,9 +1271,6 @@ def clean(self, value):
12501271
code='invalid_choice',
12511272
params={'value': val},
12521273
)
1253-
# Since this overrides the inherited ModelChoiceField.clean
1254-
# we run custom validators here
1255-
self.run_validators(value)
12561274
return qs
12571275

12581276
def prepare_value(self, value):

docs/releases/1.6.10.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,12 @@ Note, however, that this view has always carried a warning that it is not
5858
hardened for production use and should be used only as a development aid. Now
5959
may be a good time to audit your project and serve your files in production
6060
using a real front-end web server if you are not doing so.
61+
62+
Database denial-of-service with ``ModelMultipleChoiceField``
63+
============================================================
64+
65+
Given a form that uses ``ModelMultipleChoiceField`` and
66+
``show_hidden_initial=True`` (not a documented API), it was possible for a user
67+
to cause an unreasonable number of SQL queries by submitting duplicate values
68+
for the field's data. The validation logic in ``ModelMultipleChoiceField`` now
69+
deduplicates submitted values to address this issue.

docs/releases/1.7.3.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ hardened for production use and should be used only as a development aid. Now
5959
may be a good time to audit your project and serve your files in production
6060
using a real front-end web server if you are not doing so.
6161

62+
Database denial-of-service with ``ModelMultipleChoiceField``
63+
============================================================
64+
65+
Given a form that uses ``ModelMultipleChoiceField`` and
66+
``show_hidden_initial=True`` (not a documented API), it was possible for a user
67+
to cause an unreasonable number of SQL queries by submitting duplicate values
68+
for the field's data. The validation logic in ``ModelMultipleChoiceField`` now
69+
deduplicates submitted values to address this issue.
70+
6271
Bugfixes
6372
========
6473

docs/spelling_wordlist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ dbshell
134134
de
135135
deconstruct
136136
deconstructing
137+
deduplicates
137138
deepcopy
138139
deserialization
139140
deserialize

tests/model_forms/tests.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,6 +1573,27 @@ class WriterForm(forms.Form):
15731573
self.assertTrue(form.is_valid())
15741574
self.assertTrue(form.has_changed())
15751575

1576+
def test_show_hidden_initial_changed_queries_efficiently(self):
1577+
class WriterForm(forms.Form):
1578+
persons = forms.ModelMultipleChoiceField(
1579+
show_hidden_initial=True, queryset=Writer.objects.all())
1580+
1581+
writers = (Writer.objects.create(name=str(x)) for x in range(0, 50))
1582+
writer_pks = tuple(x.pk for x in writers)
1583+
form = WriterForm(data={'initial-persons': writer_pks})
1584+
with self.assertNumQueries(1):
1585+
self.assertTrue(form.has_changed())
1586+
1587+
def test_clean_does_deduplicate_values(self):
1588+
class WriterForm(forms.Form):
1589+
persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all())
1590+
1591+
person1 = Writer.objects.create(name="Person 1")
1592+
form = WriterForm(data={})
1593+
queryset = form.fields['persons'].clean([str(person1.pk)] * 50)
1594+
sql, params = queryset.query.sql_with_params()
1595+
self.assertEqual(len(params), 1)
1596+
15761597

15771598
class ModelOneToOneFieldTests(TestCase):
15781599
def test_modelform_onetoonefield(self):

0 commit comments

Comments
 (0)