High severityNVD Advisory· Published Jan 16, 2015· Updated May 6, 2026
CVE-2015-0222
CVE-2015-0222
Description
ModelMultipleChoiceField in Django 1.6.x before 1.6.10 and 1.7.x before 1.7.3, when show_hidden_initial is set to True, allows remote attackers to cause a denial of service by submitting duplicate values, which triggers a large number of SQL queries.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 1.6, < 1.6.10 | 1.6.10 |
DjangoPyPI | >= 1.7, < 1.7.3 | 1.7.3 |
Affected products
18cpe:2.3:o:canonical:ubuntu_linux:10.04:*:lts:*:*:*:*:*+ 3 more
- cpe:2.3:o:canonical:ubuntu_linux:10.04:*:lts:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:12.04:*:lts:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:14.10:*:*:*:*:*:*:*
cpe:2.3:a:djangoproject:django:1.6.8:*:*:*:*:*:*:*+ 13 more
- cpe:2.3:a:djangoproject:django:1.6.8:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.9:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <=1.4.17
- cpe:2.3:a:djangoproject:django:1.6:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.4:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.5:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.6:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.6.7:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.7.2:*:*:*:*:*:*:*
Patches
2bcfb47780ce7[1.7.x] Fixed DoS possibility in ModelMultipleChoiceField.
5 files changed · +63 −5
django/forms/models.py+23 −5 modified@@ -1221,8 +1221,7 @@ def __init__(self, queryset, cache_choices=False, required=True, def to_python(self, value): if not value: return [] - to_py = super(ModelMultipleChoiceField, self).to_python - return [to_py(val) for val in value] + return list(self._check_values(value)) def clean(self, value): if self.required and not value: @@ -1231,7 +1230,29 @@ def clean(self, value): return self.queryset.none() if not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages['list'], code='list') + qs = self._check_values(value) + # Since this overrides the inherited ModelChoiceField.clean + # we run custom validators here + self.run_validators(value) + return qs + + def _check_values(self, value): + """ + Given a list of possible PK values, returns a QuerySet of the + corresponding objects. Raises a ValidationError if a given value is + invalid (not a valid PK, not in the queryset, etc.) + """ key = self.to_field_name or 'pk' + # deduplicate given values to avoid creating many querysets or + # requiring the database backend deduplicate efficiently. + try: + value = frozenset(value) + except TypeError: + # list of lists isn't hashable, for example + raise ValidationError( + self.error_messages['list'], + code='list', + ) for pk in value: try: self.queryset.filter(**{key: pk}) @@ -1250,9 +1271,6 @@ def clean(self, value): code='invalid_choice', params={'value': val}, ) - # Since this overrides the inherited ModelChoiceField.clean - # we run custom validators here - self.run_validators(value) return qs def prepare_value(self, value):
docs/releases/1.6.10.txt+9 −0 modified@@ -58,3 +58,12 @@ Note, however, that this view has always carried a warning that it is not hardened for production use and should be used only as a development aid. Now may be a good time to audit your project and serve your files in production using a real front-end web server if you are not doing so. + +Database denial-of-service with ``ModelMultipleChoiceField`` +============================================================ + +Given a form that uses ``ModelMultipleChoiceField`` and +``show_hidden_initial=True`` (not a documented API), it was possible for a user +to cause an unreasonable number of SQL queries by submitting duplicate values +for the field's data. The validation logic in ``ModelMultipleChoiceField`` now +deduplicates submitted values to address this issue.
docs/releases/1.7.3.txt+9 −0 modified@@ -59,6 +59,15 @@ hardened for production use and should be used only as a development aid. Now may be a good time to audit your project and serve your files in production using a real front-end web server if you are not doing so. +Database denial-of-service with ``ModelMultipleChoiceField`` +============================================================ + +Given a form that uses ``ModelMultipleChoiceField`` and +``show_hidden_initial=True`` (not a documented API), it was possible for a user +to cause an unreasonable number of SQL queries by submitting duplicate values +for the field's data. The validation logic in ``ModelMultipleChoiceField`` now +deduplicates submitted values to address this issue. + Bugfixes ========
docs/spelling_wordlist+1 −0 modified@@ -134,6 +134,7 @@ dbshell de deconstruct deconstructing +deduplicates deepcopy deserialization deserialize
tests/model_forms/tests.py+21 −0 modified@@ -1573,6 +1573,27 @@ class WriterForm(forms.Form): self.assertTrue(form.is_valid()) self.assertTrue(form.has_changed()) + def test_show_hidden_initial_changed_queries_efficiently(self): + class WriterForm(forms.Form): + persons = forms.ModelMultipleChoiceField( + show_hidden_initial=True, queryset=Writer.objects.all()) + + writers = (Writer.objects.create(name=str(x)) for x in range(0, 50)) + writer_pks = tuple(x.pk for x in writers) + form = WriterForm(data={'initial-persons': writer_pks}) + with self.assertNumQueries(1): + self.assertTrue(form.has_changed()) + + def test_clean_does_deduplicate_values(self): + class WriterForm(forms.Form): + persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all()) + + person1 = Writer.objects.create(name="Person 1") + form = WriterForm(data={}) + queryset = form.fields['persons'].clean([str(person1.pk)] * 50) + sql, params = queryset.query.sql_with_params() + self.assertEqual(len(params), 1) + class ModelOneToOneFieldTests(TestCase): def test_modelform_onetoonefield(self):
d7a06ee7e571[1.6.x] Fixed DoS possibility in ModelMultipleChoiceField.
3 files changed · +53 −5
django/forms/models.py+23 −5 modified@@ -1174,8 +1174,7 @@ def __init__(self, queryset, cache_choices=False, required=True, def to_python(self, value): if not value: return [] - to_py = super(ModelMultipleChoiceField, self).to_python - return [to_py(val) for val in value] + return list(self._check_values(value)) def clean(self, value): if self.required and not value: @@ -1184,7 +1183,29 @@ def clean(self, value): return self.queryset.none() if not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages['list'], code='list') + qs = self._check_values(value) + # Since this overrides the inherited ModelChoiceField.clean + # we run custom validators here + self.run_validators(value) + return qs + + def _check_values(self, value): + """ + Given a list of possible PK values, returns a QuerySet of the + corresponding objects. Raises a ValidationError if a given value is + invalid (not a valid PK, not in the queryset, etc.) + """ key = self.to_field_name or 'pk' + # deduplicate given values to avoid creating many querysets or + # requiring the database backend deduplicate efficiently. + try: + value = frozenset(value) + except TypeError: + # list of lists isn't hashable, for example + raise ValidationError( + self.error_messages['list'], + code='list', + ) for pk in value: try: self.queryset.filter(**{key: pk}) @@ -1203,9 +1224,6 @@ def clean(self, value): code='invalid_choice', params={'value': val}, ) - # Since this overrides the inherited ModelChoiceField.clean - # we run custom validators here - self.run_validators(value) return qs def prepare_value(self, value):
docs/releases/1.6.10.txt+9 −0 modified@@ -58,3 +58,12 @@ Note, however, that this view has always carried a warning that it is not hardened for production use and should be used only as a development aid. Now may be a good time to audit your project and serve your files in production using a real front-end web server if you are not doing so. + +Database denial-of-service with ``ModelMultipleChoiceField`` +============================================================ + +Given a form that uses ``ModelMultipleChoiceField`` and +``show_hidden_initial=True`` (not a documented API), it was possible for a user +to cause an unreasonable number of SQL queries by submitting duplicate values +for the field's data. The validation logic in ``ModelMultipleChoiceField`` now +deduplicates submitted values to address this issue.
tests/model_forms/tests.py+21 −0 modified@@ -1381,6 +1381,27 @@ def test_with_data(self): </select></p> <p><label for="id_age">Age:</label> <input type="number" name="age" value="65" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) + def test_show_hidden_initial_changed_queries_efficiently(self): + class WriterForm(forms.Form): + persons = forms.ModelMultipleChoiceField( + show_hidden_initial=True, queryset=Writer.objects.all()) + + writers = (Writer.objects.create(name=str(x)) for x in range(0, 50)) + writer_pks = tuple(x.pk for x in writers) + form = WriterForm(data={'initial-persons': writer_pks}) + with self.assertNumQueries(1): + self.assertTrue(form.has_changed()) + + def test_clean_does_deduplicate_values(self): + class WriterForm(forms.Form): + persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all()) + + person1 = Writer.objects.create(name="Person 1") + form = WriterForm(data={}) + queryset = form.fields['persons'].clean([str(person1.pk)] * 50) + sql, params = queryset.query.sql_with_params() + self.assertEqual(len(params), 1) + def test_file_field(self): # Test conditions when files is either not given or empty.
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
19- ubuntu.com/usn/usn-2469-1nvdPatchVendor AdvisoryWEB
- www.djangoproject.com/weblog/2015/jan/13/security/nvdPatchVendor Advisory
- github.com/advisories/GHSA-6g95-x6cj-mg4vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-0222ghsaADVISORY
- advisories.mageia.org/MGASA-2015-0026.htmlnvdWEB
- lists.fedoraproject.org/pipermail/package-announce/2015-January/148485.htmlnvdWEB
- lists.fedoraproject.org/pipermail/package-announce/2015-January/148608.htmlnvdWEB
- lists.fedoraproject.org/pipermail/package-announce/2015-January/148696.htmlnvdWEB
- lists.opensuse.org/opensuse-updates/2015-04/msg00001.htmlnvdWEB
- lists.opensuse.org/opensuse-updates/2015-09/msg00035.htmlnvdWEB
- www.mandriva.com/security/advisoriesnvdWEB
- github.com/django/django/commit/bcfb47780ce7caecb409a9e9c1c314266e41d392ghsaWEB
- github.com/django/django/commit/d7a06ee7e571b6dad07c0f5b519b1db02e2a476cghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2015-7.yamlghsaWEB
- web.archive.org/web/20161201073154/http://secunia.com/advisories/62285ghsaWEB
- web.archive.org/web/20161201073337/http://secunia.com/advisories/62309ghsaWEB
- www.djangoproject.com/weblog/2015/jan/13/securityghsaWEB
- secunia.com/advisories/62285nvd
- secunia.com/advisories/62309nvd
News mentions
0No linked articles in our index yet.