[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 75c0d4ea3a
from master
This commit is contained in:
parent
380545bf85
commit
34526c2f56
|
@ -1509,6 +1509,12 @@ class FilePathField(Field):
|
||||||
del kwargs["max_length"]
|
del kwargs["max_length"]
|
||||||
return name, path, args, kwargs
|
return name, path, args, kwargs
|
||||||
|
|
||||||
|
def get_prep_value(self, value):
|
||||||
|
value = super(FilePathField, self).get_prep_value(value)
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return six.text_type(value)
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
'path': self.path,
|
'path': self.path,
|
||||||
|
@ -1642,6 +1648,12 @@ class IPAddressField(Field):
|
||||||
del kwargs['max_length']
|
del kwargs['max_length']
|
||||||
return name, path, args, kwargs
|
return name, path, args, kwargs
|
||||||
|
|
||||||
|
def get_prep_value(self, value):
|
||||||
|
value = super(IPAddressField, self).get_prep_value(value)
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return six.text_type(value)
|
||||||
|
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
return "IPAddressField"
|
return "IPAddressField"
|
||||||
|
|
||||||
|
@ -1711,12 +1723,14 @@ class GenericIPAddressField(Field):
|
||||||
|
|
||||||
def get_prep_value(self, value):
|
def get_prep_value(self, value):
|
||||||
value = super(GenericIPAddressField, self).get_prep_value(value)
|
value = super(GenericIPAddressField, self).get_prep_value(value)
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
if value and ':' in value:
|
if value and ':' in value:
|
||||||
try:
|
try:
|
||||||
return clean_ipv6_address(value, self.unpack_ipv4)
|
return clean_ipv6_address(value, self.unpack_ipv4)
|
||||||
except exceptions.ValidationError:
|
except exceptions.ValidationError:
|
||||||
pass
|
pass
|
||||||
return value
|
return six.text_type(value)
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
|
|
|
@ -593,6 +593,17 @@ For example::
|
||||||
return ''.join([''.join(l) for l in (value.north,
|
return ''.join([''.join(l) for l in (value.north,
|
||||||
value.east, value.south, value.west)])
|
value.east, value.south, value.west)])
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If your custom field uses the ``CHAR``, ``VARCHAR`` or ``TEXT``
|
||||||
|
types for MySQL, you must make sure that :meth:`.get_prep_value`
|
||||||
|
always returns a string type. MySQL performs flexible and unexpected
|
||||||
|
matching when a query is performed on these types and the provided
|
||||||
|
value is an integer, which can cause queries to include unexpected
|
||||||
|
objects in their results. This problem cannot occur if you always
|
||||||
|
return a string type from :meth:`.get_prep_value`.
|
||||||
|
|
||||||
|
|
||||||
Converting query values to database values
|
Converting query values to database values
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -532,6 +532,22 @@ MySQL does not support the ``NOWAIT`` option to the ``SELECT ... FOR UPDATE``
|
||||||
statement. If ``select_for_update()`` is used with ``nowait=True`` then a
|
statement. If ``select_for_update()`` is used with ``nowait=True`` then a
|
||||||
``DatabaseError`` will be raised.
|
``DatabaseError`` will be raised.
|
||||||
|
|
||||||
|
Automatic typecasting can cause unexpected results
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
When performing a query on a string type, but with an integer value, MySQL will
|
||||||
|
coerce the types of all values in the table to an integer before performing the
|
||||||
|
comparison. If your table contains the values ``'abc'``, ``'def'`` and you
|
||||||
|
query for ``WHERE mycolumn=0``, both rows will match. Similarly, ``WHERE mycolumn=1``
|
||||||
|
will match the value ``'abc1'``. Therefore, string type fields included in Django
|
||||||
|
will always cast the value to a string before using it in a query.
|
||||||
|
|
||||||
|
If you implement custom model fields that inherit from :class:`~django.db.models.Field`
|
||||||
|
directly, are overriding :meth:`~django.db.models.Field.get_prep_value`, or use
|
||||||
|
:meth:`extra() <django.db.models.query.QuerySet.extra>` or
|
||||||
|
:meth:`raw() <django.db.models.Manager.raw>`, you should ensure that you
|
||||||
|
perform the appropriate typecasting.
|
||||||
|
|
||||||
.. _sqlite-notes:
|
.. _sqlite-notes:
|
||||||
|
|
||||||
SQLite notes
|
SQLite notes
|
||||||
|
|
|
@ -1202,6 +1202,16 @@ of the arguments is required, but you should use at least one of them.
|
||||||
|
|
||||||
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
|
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If you are performing queries on MySQL, note that MySQL's silent type coercion
|
||||||
|
may cause unexpected results when mixing types. If you query on a string
|
||||||
|
type column, but with an integer value, MySQL will coerce the types of all values
|
||||||
|
in the table to an integer before performing the comparison. For example, if your
|
||||||
|
table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
|
||||||
|
both rows will match. To prevent this, perform the correct typecasting
|
||||||
|
before using the value in a query.
|
||||||
|
|
||||||
defer
|
defer
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,16 @@ options that make it very powerful.
|
||||||
database, but does nothing to enforce that. If the query does not
|
database, but does nothing to enforce that. If the query does not
|
||||||
return rows, a (possibly cryptic) error will result.
|
return rows, a (possibly cryptic) error will result.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If you are performing queries on MySQL, note that MySQL's silent type coercion
|
||||||
|
may cause unexpected results when mixing types. If you query on a string
|
||||||
|
type column, but with an integer value, MySQL will coerce the types of all values
|
||||||
|
in the table to an integer before performing the comparison. For example, if your
|
||||||
|
table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``,
|
||||||
|
both rows will match. To prevent this, perform the correct typecasting
|
||||||
|
before using the value in a query.
|
||||||
|
|
||||||
Mapping query fields to model fields
|
Mapping query fields to model fields
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -673,12 +673,20 @@ class PromiseTest(test.TestCase):
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
CharField().get_prep_value(lazy_func()),
|
CharField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
CharField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_CommaSeparatedIntegerField(self):
|
def test_CommaSeparatedIntegerField(self):
|
||||||
lazy_func = lazy(lambda: '1,2', six.text_type)
|
lazy_func = lazy(lambda: '1,2', six.text_type)
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
CommaSeparatedIntegerField().get_prep_value(lazy_func()),
|
CommaSeparatedIntegerField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
CommaSeparatedIntegerField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_DateField(self):
|
def test_DateField(self):
|
||||||
lazy_func = lazy(lambda: datetime.date.today(), datetime.date)
|
lazy_func = lazy(lambda: datetime.date.today(), datetime.date)
|
||||||
|
@ -709,12 +717,20 @@ class PromiseTest(test.TestCase):
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
FileField().get_prep_value(lazy_func()),
|
FileField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
FileField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_FilePathField(self):
|
def test_FilePathField(self):
|
||||||
lazy_func = lazy(lambda: 'tests.py', six.text_type)
|
lazy_func = lazy(lambda: 'tests.py', six.text_type)
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
FilePathField().get_prep_value(lazy_func()),
|
FilePathField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
FilePathField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_FloatField(self):
|
def test_FloatField(self):
|
||||||
lazy_func = lazy(lambda: 1.2, float)
|
lazy_func = lazy(lambda: 1.2, float)
|
||||||
|
@ -735,9 +751,13 @@ class PromiseTest(test.TestCase):
|
||||||
int)
|
int)
|
||||||
|
|
||||||
def test_IPAddressField(self):
|
def test_IPAddressField(self):
|
||||||
lazy_func = lazy(lambda: '127.0.0.1', six.text_type)
|
|
||||||
with warnings.catch_warnings(record=True):
|
with warnings.catch_warnings(record=True):
|
||||||
warnings.simplefilter("always")
|
warnings.simplefilter("always")
|
||||||
|
lazy_func = lazy(lambda: '127.0.0.1', six.text_type)
|
||||||
|
self.assertIsInstance(
|
||||||
|
IPAddressField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
IPAddressField().get_prep_value(lazy_func()),
|
IPAddressField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
@ -747,6 +767,10 @@ class PromiseTest(test.TestCase):
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
GenericIPAddressField().get_prep_value(lazy_func()),
|
GenericIPAddressField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
GenericIPAddressField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_NullBooleanField(self):
|
def test_NullBooleanField(self):
|
||||||
lazy_func = lazy(lambda: True, bool)
|
lazy_func = lazy(lambda: True, bool)
|
||||||
|
@ -771,6 +795,10 @@ class PromiseTest(test.TestCase):
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
SlugField().get_prep_value(lazy_func()),
|
SlugField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
SlugField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_SmallIntegerField(self):
|
def test_SmallIntegerField(self):
|
||||||
lazy_func = lazy(lambda: 1, int)
|
lazy_func = lazy(lambda: 1, int)
|
||||||
|
@ -783,6 +811,10 @@ class PromiseTest(test.TestCase):
|
||||||
self.assertIsInstance(
|
self.assertIsInstance(
|
||||||
TextField().get_prep_value(lazy_func()),
|
TextField().get_prep_value(lazy_func()),
|
||||||
six.text_type)
|
six.text_type)
|
||||||
|
lazy_func = lazy(lambda: 0, int)
|
||||||
|
self.assertIsInstance(
|
||||||
|
TextField().get_prep_value(lazy_func()),
|
||||||
|
six.text_type)
|
||||||
|
|
||||||
def test_TimeField(self):
|
def test_TimeField(self):
|
||||||
lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time)
|
lazy_func = lazy(lambda: datetime.datetime.now().time(), datetime.time)
|
||||||
|
|
Loading…
Reference in New Issue