diff options
| author | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2019-07-22 10:45:26 +0200 |
|---|---|---|
| committer | Carlton Gibson <carlton.gibson@noumenal.es> | 2019-07-31 21:29:17 +0200 |
| commit | ed682a24fca774818542757651bfba576c3fc3ef (patch) | |
| tree | d0e56c1ce9356efc4cd7c910a1fde55f9b133971 | |
| parent | 52479acce792ad80bb0f915f20b835f919993c72 (diff) | |
[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.
| -rw-r--r-- | django/contrib/postgres/fields/hstore.py | 2 | ||||
| -rw-r--r-- | django/contrib/postgres/fields/jsonb.py | 8 | ||||
| -rw-r--r-- | docs/releases/1.11.23.txt | 9 | ||||
| -rw-r--r-- | tests/postgres_tests/test_hstore.py | 15 | ||||
| -rw-r--r-- | tests/postgres_tests/test_json.py | 14 |
5 files changed, 41 insertions, 7 deletions
diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index 605deaf62c..b77d1b1958 100644 --- a/django/contrib/postgres/fields/hstore.py +++ b/django/contrib/postgres/fields/hstore.py @@ -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): diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index 0722a05a69..d7a22591e2 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -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): diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt index c95ffd9a50..03b33ebf63 100644 --- a/docs/releases/1.11.23.txt +++ b/docs/releases/1.11.23.txt @@ -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()``. diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py index 0fc427f67c..dd8e642ed0 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -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\\"}"}, ' diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index 4e8851d485..925e800131 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -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): |
