Skip to content

Commit ed682a2

Browse files
felixxmcarltongibson
authored andcommitted
[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.
1 parent 52479ac commit ed682a2

File tree

5 files changed

+41
-7
lines changed

5 files changed

+41
-7
lines changed

django/contrib/postgres/fields/hstore.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def __init__(self, key_name, *args, **kwargs):
8686

8787
def as_sql(self, compiler, connection):
8888
lhs, params = compiler.compile(self.lhs)
89-
return "(%s -> '%s')" % (lhs, self.key_name), params
89+
return '(%s -> %%s)' % lhs, [self.key_name] + params
9090

9191

9292
class KeyTransformFactory(object):

django/contrib/postgres/fields/jsonb.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,10 @@ def as_sql(self, compiler, connection):
104104
if len(key_transforms) > 1:
105105
return "(%s %s %%s)" % (lhs, self.nested_operator), [key_transforms] + params
106106
try:
107-
int(self.key_name)
107+
lookup = int(self.key_name)
108108
except ValueError:
109-
lookup = "'%s'" % self.key_name
110-
else:
111-
lookup = "%s" % self.key_name
112-
return "(%s %s %s)" % (lhs, self.operator, lookup), params
109+
lookup = self.key_name
110+
return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params
113111

114112

115113
class KeyTextTransform(KeyTransform):

docs/releases/1.11.23.txt

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

tests/postgres_tests/test_hstore.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
import json
55

66
from django.core import exceptions, serializers
7+
from django.db import connection
78
from django.forms import Form
8-
from django.test.utils import modify_settings
9+
from django.test.utils import CaptureQueriesContext, modify_settings
910

1011
from . import PostgreSQLTestCase
1112
from .models import HStoreModel
@@ -167,6 +168,18 @@ def test_usage_in_subquery(self):
167168
self.objs[:2]
168169
)
169170

171+
def test_key_sql_injection(self):
172+
with CaptureQueriesContext(connection) as queries:
173+
self.assertFalse(
174+
HStoreModel.objects.filter(**{
175+
"field__test' = 'a') OR 1 = 1 OR ('d": 'x',
176+
}).exists()
177+
)
178+
self.assertIn(
179+
"""."field" -> 'test'' = ''a'') OR 1 = 1 OR (''d') = 'x' """,
180+
queries[0]['sql'],
181+
)
182+
170183

171184
class TestSerialization(HStoreTestCase):
172185
test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, '

tests/postgres_tests/test_json.py

+14
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
from django.core import exceptions, serializers
88
from django.core.serializers.json import DjangoJSONEncoder
9+
from django.db import connection
910
from django.forms import CharField, Form, widgets
1011
from django.test import skipUnlessDBFeature
12+
from django.test.utils import CaptureQueriesContext
1113
from django.utils.html import escape
1214

1315
from . import PostgreSQLTestCase
@@ -263,6 +265,18 @@ def test_regex(self):
263265
def test_iregex(self):
264266
self.assertTrue(JSONModel.objects.filter(field__foo__iregex=r'^bAr$').exists())
265267

268+
def test_key_sql_injection(self):
269+
with CaptureQueriesContext(connection) as queries:
270+
self.assertFalse(
271+
JSONModel.objects.filter(**{
272+
"""field__test' = '"a"') OR 1 = 1 OR ('d""": 'x',
273+
}).exists()
274+
)
275+
self.assertIn(
276+
"""."field" -> 'test'' = ''"a"'') OR 1 = 1 OR (''d') = '"x"' """,
277+
queries[0]['sql'],
278+
)
279+
266280

267281
@skipUnlessDBFeature('has_jsonb_datatype')
268282
class TestSerialization(PostgreSQLTestCase):

0 commit comments

Comments
 (0)