Skip to content

Commit

Permalink
[1.11.x] Fixed CVE-2019-14234 -- Protected JSONField/HStoreField key …
Browse files Browse the repository at this point in the history
…and index lookups against SQL injection.

Thanks to Sage M. Abdullah for the report and initial patch.
Thanks Florian Apolloner for reviews.
  • Loading branch information
felixxm authored and carltongibson committed Jul 31, 2019
1 parent 52479ac commit ed682a2
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 7 deletions.
2 changes: 1 addition & 1 deletion django/contrib/postgres/fields/hstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __init__(self, key_name, *args, **kwargs):

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):
Expand Down
8 changes: 3 additions & 5 deletions django/contrib/postgres/fields/jsonb.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,10 @@ def as_sql(self, compiler, connection):
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):
Expand Down
9 changes: 9 additions & 0 deletions docs/releases/1.11.23.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()``.
15 changes: 14 additions & 1 deletion tests/postgres_tests/test_hstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
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
Expand Down Expand Up @@ -167,6 +168,18 @@ def test_usage_in_subquery(self):
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\\"}"}, '
Expand Down
14 changes: 14 additions & 0 deletions tests/postgres_tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

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
Expand Down Expand Up @@ -263,6 +265,18 @@ def test_regex(self):
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):
Expand Down

0 comments on commit ed682a2

Please sign in to comment.