티스토리 수익 글 보기

티스토리 수익 글 보기

[1.7.x] Fixed queries that may return unexpected results on MySQL due… · django/django@34526c2 · GitHub
Skip to content
/ django Public

Commit 34526c2

Browse files
mxsashatimgraham
authored andcommitted
[1.7.x] Fixed queries that may return unexpected results on MySQL due to typecasting.
This is a security fix. Disclosure will follow shortly. Backport of 75c0d4e from master
1 parent 380545b commit 34526c2

File tree

6 files changed

+95
2
lines changed

6 files changed

+95
2
lines changed

django/db/models/fields/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,12 @@ def deconstruct(self):
15091509
del kwargs["max_length"]
15101510
return name, path, args, kwargs
15111511

1512+
def get_prep_value(self, value):
1513+
value = super(FilePathField, self).get_prep_value(value)
1514+
if value is None:
1515+
return None
1516+
return six.text_type(value)
1517+
15121518
def formfield(self, **kwargs):
15131519
defaults = {
15141520
'path': self.path,
@@ -1642,6 +1648,12 @@ def deconstruct(self):
16421648
del kwargs['max_length']
16431649
return name, path, args, kwargs
16441650

1651+
def get_prep_value(self, value):
1652+
value = super(IPAddressField, self).get_prep_value(value)
1653+
if value is None:
1654+
return None
1655+
return six.text_type(value)
1656+
16451657
def get_internal_type(self):
16461658
return "IPAddressField"
16471659

@@ -1711,12 +1723,14 @@ def get_db_prep_value(self, value, connection, prepared=False):
17111723

17121724
def get_prep_value(self, value):
17131725
value = super(GenericIPAddressField, self).get_prep_value(value)
1726+
if value is None:
1727+
return None
17141728
if value and ':' in value:
17151729
try:
17161730
return clean_ipv6_address(value, self.unpack_ipv4)
17171731
except exceptions.ValidationError:
17181732
pass
1719-
return value
1733+
return six.text_type(value)
17201734

17211735
def formfield(self, **kwargs):
17221736
defaults = {

docs/howto/custom-model-fields.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,17 @@ For example::
593593
return ''.join([''.join(l) for l in (value.north,
594594
value.east, value.south, value.west)])
595595

596+
.. warning::
597+
598+
If your custom field uses the ``CHAR``, ``VARCHAR`` or ``TEXT``
599+
types for MySQL, you must make sure that :meth:`.get_prep_value`
600+
always returns a string type. MySQL performs flexible and unexpected
601+
matching when a query is performed on these types and the provided
602+
value is an integer, which can cause queries to include unexpected
603+
objects in their results. This problem cannot occur if you always
604+
return a string type from :meth:`.get_prep_value`.
605+
606+
596607
Converting query values to database values
597608
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
598609

docs/ref/databases.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,22 @@ MySQL does not support the ``NOWAIT`` option to the ``SELECT ... FOR UPDATE``
532532
statement. If ``select_for_update()`` is used with ``nowait=True`` then a
533533
``DatabaseError`` will be raised.
534534

535+
Automatic typecasting can cause unexpected results
536+
--------------------------------------------------
537+
538+
When performing a query on a string type, but with an integer value, MySQL will
539+
coerce the types of all values in the table to an integer before performing the
540+
comparison. If your table contains the values ``'abc'``, ``'def'`` and you
541+
query for ``WHERE mycolumn=0``, both rows will match. Similarly, ``WHERE mycolumn=1``
542+
will match the value ``'abc1'``. Therefore, string type fields included in Django
543+
will always cast the value to a string before using it in a query.
544+
545+
If you implement custom model fields that inherit from :class:`~django.db.models.Field`
546+
directly, are overriding :meth:`~django.db.models.Field.get_prep_value`, or use
547+
:meth:`extra() <django.db.models.query.QuerySet.extra>` or
548+
:meth:`raw() <django.db.models.Manager.raw>`, you should ensure that you
549+
perform the appropriate typecasting.
550+
535551
.. _sqlite-notes:
536552

537553
SQLite notes

docs/ref/models/querysets.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,16 @@ of the arguments is required, but you should use at least one of them.
12021202

12031203
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
12041204

1205+
.. warning::
1206+
1207+
If you are performing queries on MySQL, note that MySQL's silent type coercion
1208+
may cause unexpected results when mixing types. If you query on a string
1209+
type column, but with an integer value, MySQL will coerce the types of all values
1210+
in the table to an integer before performing the comparison. For example, if your
1211+
table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
1212+
both rows will match. To prevent this, perform the correct typecasting
1213+
before using the value in a query.
1214+
12051215
defer
12061216
~~~~~
12071217

docs/topics/db/sql.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ options that make it very powerful.
6666
database, but does nothing to enforce that. If the query does not
6767
return rows, a (possibly cryptic) error will result.
6868

69+
.. warning::
70+
71+
If you are performing queries on MySQL, note that MySQL's silent type coercion
72+
may cause unexpected results when mixing types. If you query on a string
73+
type column, but with an integer value, MySQL will coerce the types of all values
74+
in the table to an integer before performing the comparison. For example, if your
75+
table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
76+
both rows will match. To prevent this, perform the correct typecasting
77+
before using the value in a query.
78+
6979
Mapping query fields to model fields
7080
------------------------------------
7181

tests/model_fields/tests.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,12 +673,20 @@ def test_CharField(self):
673673
self.assertIsInstance(
674674
CharField().get_prep_value(lazy_func()),
675675
six.text_type)
676+
lazy_func = lazy(lambda: 0, int)
677+
self.assertIsInstance(
678+
CharField().get_prep_value(lazy_func()),
679+
six.text_type)
676680

677681
def test_CommaSeparatedIntegerField(self):
678682
lazy_func = lazy(lambda: '1,2', six.text_type)
679683
self.assertIsInstance(
680684
CommaSeparatedIntegerField().get_prep_value(lazy_func()),
681685
six.text_type)
686+
lazy_func = lazy(lambda: 0, int)
687+
self.assertIsInstance(
688+
CommaSeparatedIntegerField().get_prep_value(lazy_func()),
689+
six.text_type)
682690

683691
def test_DateField(self):
684692
lazy_func = lazy(lambda: datetime.date.today(), datetime.date)
@@ -709,12 +717,20 @@ def test_FileField(self):
709717
self.assertIsInstance(
710718
FileField().get_prep_value(lazy_func()),
711719
six.text_type)
720+
lazy_func = lazy(lambda: 0, int)
721+
self.assertIsInstance(
722+
FileField().get_prep_value(lazy_func()),
723+
six.text_type)
712724

713725
def test_FilePathField(self):
714726
lazy_func = lazy(lambda: 'tests.py', six.text_type)
715727
self.assertIsInstance(
716728
FilePathField().get_prep_value(lazy_func()),
717729
six.text_type)
730+
lazy_func = lazy(lambda: 0, int)
731+
self.assertIsInstance(
732+
FilePathField().get_prep_value(lazy_func()),
733+
six.text_type)
718734

719735
def test_FloatField(self):
720736
lazy_func = lazy(lambda: 1.2, float)
@@ -735,9 +751,13 @@ def test_IntegerField(self):
735751
int)
736752

737753
def test_IPAddressField(self):
738-
lazy_func = lazy(lambda: '127.0.0.1', six.text_type)
739754
with warnings.catch_warnings(record=True):
740755
warnings.simplefilter("always")
756+
lazy_func = lazy(lambda: '127.0.0.1', six.text_type)
757+
self.assertIsInstance(
758+
IPAddressField().get_prep_value(lazy_func()),
759+
six.text_type)
760+
lazy_func = lazy(lambda: 0, int)
741761
self.assertIsInstance(
742762
IPAddressField().get_prep_value(lazy_func()),
743763
six.text_type)
@@ -747,6 +767,10 @@ def test_GenericIPAddressField(self):
747767
self.assertIsInstance(
748768
GenericIPAddressField().get_prep_value(lazy_func()),
749769
six.text_type)
770+
lazy_func = lazy(lambda: 0, int)
771+
self.assertIsInstance(
772+
GenericIPAddressField().get_prep_value(lazy_func()),
773+
six.text_type)
750774

751775
def test_NullBooleanField(self):
752776
lazy_func = lazy(lambda: True, bool)
@@ -771,6 +795,10 @@ def test_SlugField(self):
771795
self.assertIsInstance(
772796
SlugField().get_prep_value(lazy_func()),
773797
six.text_type)
798+
lazy_func = lazy(lambda: 0, int)
799+
self.assertIsInstance(
800+
SlugField().get_prep_value(lazy_func()),
801+
six.text_type)
774802

775803
def test_SmallIntegerField(self):
776804
lazy_func = lazy(lambda: 1, int)
@@ -783,6 +811,10 @@ def test_TextField(self):
783811
self.assertIsInstance(
784812
TextField().get_prep_value(lazy_func()),
785813
six.text_type)
814+
lazy_func = lazy(lambda: 0, int)
815+
self.assertIsInstance(
816+
TextField().get_prep_value(lazy_func()),
817+
six.text_type)
786818

787819
def test_TimeField(self):
788820
lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time)

0 commit comments

Comments
 (0)