[1.11.x] Fixed CVE-2019-14234 -- Protected JSONField/HStoreField key and index lookups against SQL injection.

Thanks to Sage M. Abdullah for the report and initial patch.
Thanks Florian Apolloner for reviews.
This commit is contained in:
Mariusz Felisiak 2019-07-22 10:45:26 +02:00 committed by Carlton Gibson
parent 52479acce7
commit ed682a24fc
5 changed files with 41 additions and 7 deletions

View File

@ -86,7 +86,7 @@ class KeyTransform(Transform):
def as_sql(self, compiler, connection):
lhs, params = compiler.compile(self.lhs)
return "(%s -> '%s')" % (lhs, self.key_name), params
return '(%s -> %%s)' % lhs, [self.key_name] + params
class KeyTransformFactory(object):

View File

@ -104,12 +104,10 @@ class KeyTransform(Transform):
if len(key_transforms) > 1:
return "(%s %s %%s)" % (lhs, self.nested_operator), [key_transforms] + params
try:
int(self.key_name)
lookup = int(self.key_name)
except ValueError:
lookup = "'%s'" % self.key_name
else:
lookup = "%s" % self.key_name
return "(%s %s %s)" % (lhs, self.operator, lookup), params
lookup = self.key_name
return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params
class KeyTextTransform(KeyTransform):

View File

@ -36,3 +36,12 @@ Remember that absolutely NO guarantee is provided about the results of
``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
``strip_tags()`` call without escaping it first, for example with
:func:`django.utils.html.escape`.
CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONField``/``HStoreField``
====================================================================================================
:lookup:`Key and index lookups <jsonfield.key>` for
:class:`~django.contrib.postgres.fields.JSONField` and :lookup:`key lookups
<hstorefield.key>` for :class:`~django.contrib.postgres.fields.HStoreField`
were subject to SQL injection, using a suitably crafted dictionary, with
dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``.

View File

@ -4,8 +4,9 @@ from __future__ import unicode_literals
import json
from django.core import exceptions, serializers
from django.db import connection
from django.forms import Form
from django.test.utils import modify_settings
from django.test.utils import CaptureQueriesContext, modify_settings
from . import PostgreSQLTestCase
from .models import HStoreModel
@ -167,6 +168,18 @@ class TestQuerying(HStoreTestCase):
self.objs[:2]
)
def test_key_sql_injection(self):
with CaptureQueriesContext(connection) as queries:
self.assertFalse(
HStoreModel.objects.filter(**{
"field__test' = 'a') OR 1 = 1 OR ('d": 'x',
}).exists()
)
self.assertIn(
"""."field" -> 'test'' = ''a'') OR 1 = 1 OR (''d') = 'x' """,
queries[0]['sql'],
)
class TestSerialization(HStoreTestCase):
test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, '

View File

@ -6,8 +6,10 @@ from decimal import Decimal
from django.core import exceptions, serializers
from django.core.serializers.json import DjangoJSONEncoder
from django.db import connection
from django.forms import CharField, Form, widgets
from django.test import skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext
from django.utils.html import escape
from . import PostgreSQLTestCase
@ -263,6 +265,18 @@ class TestQuerying(PostgreSQLTestCase):
def test_iregex(self):
self.assertTrue(JSONModel.objects.filter(field__foo__iregex=r'^bAr$').exists())
def test_key_sql_injection(self):
with CaptureQueriesContext(connection) as queries:
self.assertFalse(
JSONModel.objects.filter(**{
"""field__test' = '"a"') OR 1 = 1 OR ('d""": 'x',
}).exists()
)
self.assertIn(
"""."field" -> 'test'' = ''"a"'') OR 1 = 1 OR (''d') = '"x"' """,
queries[0]['sql'],
)
@skipUnlessDBFeature('has_jsonb_datatype')
class TestSerialization(PostgreSQLTestCase):