summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2019-07-22 10:45:26 +0200
committerCarlton Gibson <carlton.gibson@noumenal.es>2019-07-31 21:29:17 +0200
commited682a24fca774818542757651bfba576c3fc3ef (patch)
treed0e56c1ce9356efc4cd7c910a1fde55f9b133971
parent52479acce792ad80bb0f915f20b835f919993c72 (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.py2
-rw-r--r--django/contrib/postgres/fields/jsonb.py8
-rw-r--r--docs/releases/1.11.23.txt9
-rw-r--r--tests/postgres_tests/test_hstore.py15
-rw-r--r--tests/postgres_tests/test_json.py14
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):