VYPR
Critical severityNVD Advisory· Published Aug 9, 2019· Updated Aug 5, 2024

CVE-2019-14234

CVE-2019-14234

Description

An issue was discovered in Django 1.11.x before 1.11.23, 2.1.x before 2.1.11, and 2.2.x before 2.2.4. Due to an error in shallow key transformation, key and index lookups for django.contrib.postgres.fields.JSONField, and key lookups for django.contrib.postgres.fields.HStoreField, were subject to SQL injection. This could, for example, be exploited via crafted use of "OR 1=1" in a key or index name to return all records, using a suitably crafted dictionary, with dictionary expansion, as the **kwargs passed to the QuerySet.filter() function.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Django JSONField and HStoreField key lookups were vulnerable to SQL injection due to improper sanitization of key names, allowing attackers to execute arbitrary SQL via crafted dictionary keys.

Root

Cause

The vulnerability stems from an error in Django's shallow key transformation for django.contrib.postgres.fields.JSONField and HStoreField. When performing key or index lookups, the key name was directly interpolated into the SQL query without proper escaping, allowing an attacker to inject arbitrary SQL [2][3]. The fix, as shown in the commit, changes the SQL generation to use parameterized queries instead of string formatting [3].

Exploitation

An attacker can exploit this by passing a specially crafted dictionary as the **kwargs to QuerySet.filter(). For example, using a key name like "OR 1=1" would cause the query to return all records from the table. The attack requires the ability to control the key names in a dictionary that is expanded into keyword arguments, which is possible in applications that accept user input for field names or use dictionary expansion [2][4].

Impact

Successful exploitation allows an attacker to perform SQL injection, potentially retrieving, modifying, or deleting arbitrary data from the database. The severity is high because it can lead to complete compromise of the database backend [2][4].

Mitigation

The issue is fixed in Django versions 1.11.23, 2.1.11, and 2.2.4. Users should upgrade immediately. No workaround is available other than upgrading [2][3][4].

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 1.11a1, < 1.11.231.11.23
DjangoPyPI
>= 2.1a1, < 2.1.112.1.11
DjangoPyPI
>= 2.2a1, < 2.2.42.2.4

Affected products

10

Patches

3
ed682a24fca7

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

https://github.com/django/djangoMariusz FelisiakJul 22, 2019via ghsa
5 files changed · +41 7
  • django/contrib/postgres/fields/hstore.py+1 1 modified
    @@ -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):
    
  • django/contrib/postgres/fields/jsonb.py+3 5 modified
    @@ -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):
    
  • docs/releases/1.11.23.txt+9 0 modified
    @@ -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()``.
    
  • tests/postgres_tests/test_hstore.py+14 1 modified
    @@ -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
    @@ -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\\"}"}, '
    
  • tests/postgres_tests/test_json.py+14 0 modified
    @@ -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
    @@ -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):
    
4f5b58f5cd3c

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

https://github.com/django/djangoMariusz FelisiakJul 22, 2019via ghsa
7 files changed · +59 8
  • django/contrib/postgres/fields/hstore.py+1 1 modified
    @@ -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:
    
  • django/contrib/postgres/fields/jsonb.py+3 5 modified
    @@ -109,12 +109,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):
    
  • docs/releases/1.11.23.txt+9 0 modified
    @@ -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()``.
    
  • docs/releases/2.1.11.txt+9 0 modified
    @@ -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()``.
    
  • docs/releases/2.2.4.txt+9 0 modified
    @@ -37,6 +37,15 @@ Remember that absolutely NO guarantee is provided about the results of
     ``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()``.
    +
     Bugfixes
     ========
     
    
  • tests/postgres_tests/test_hstore.py+14 1 modified
    @@ -1,8 +1,9 @@
     import json
     
     from django.core import checks, exceptions, serializers
    +from django.db import connection
     from django.forms import Form
    -from django.test.utils import isolate_apps
    +from django.test.utils import CaptureQueriesContext, isolate_apps
     
     from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
     from .models import HStoreModel, PostgreSQLModel
    @@ -185,6 +186,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'],
    +        )
    +
     
     @isolate_apps('postgres_tests')
     class TestChecks(PostgreSQLSimpleTestCase):
    
  • tests/postgres_tests/test_json.py+14 1 modified
    @@ -5,9 +5,10 @@
     
     from django.core import checks, exceptions, serializers
     from django.core.serializers.json import DjangoJSONEncoder
    +from django.db import connection
     from django.db.models import Count, Q
     from django.forms import CharField, Form, widgets
    -from django.test.utils import isolate_apps
    +from django.test.utils import CaptureQueriesContext, isolate_apps
     from django.utils.html import escape
     
     from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase
    @@ -322,6 +323,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'],
    +        )
    +
     
     @isolate_apps('postgres_tests')
     class TestChecks(PostgreSQLSimpleTestCase):
    
f74b3ae3628c

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

https://github.com/django/djangoMariusz FelisiakJul 22, 2019via ghsa
6 files changed · +52 8
  • django/contrib/postgres/fields/hstore.py+1 1 modified
    @@ -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:
    
  • django/contrib/postgres/fields/jsonb.py+3 5 modified
    @@ -109,12 +109,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):
    
  • docs/releases/1.11.23.txt+9 0 modified
    @@ -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()``.
    
  • docs/releases/2.1.11.txt+9 0 modified
    @@ -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()``.
    
  • tests/postgres_tests/test_hstore.py+16 1 modified
    @@ -1,8 +1,11 @@
     import json
     
     from django.core import checks, exceptions, serializers
    +from django.db import connection
     from django.forms import Form
    -from django.test.utils import isolate_apps, modify_settings
    +from django.test.utils import (
    +    CaptureQueriesContext, isolate_apps, modify_settings,
    +)
     
     from . import PostgreSQLTestCase
     from .models import HStoreModel, PostgreSQLModel
    @@ -189,6 +192,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'],
    +        )
    +
     
     @isolate_apps('postgres_tests')
     class TestChecks(PostgreSQLTestCase):
    
  • tests/postgres_tests/test_json.py+14 1 modified
    @@ -4,9 +4,10 @@
     
     from django.core import checks, exceptions, serializers
     from django.core.serializers.json import DjangoJSONEncoder
    +from django.db import connection
     from django.db.models import Q
     from django.forms import CharField, Form, widgets
    -from django.test.utils import isolate_apps
    +from django.test.utils import CaptureQueriesContext, isolate_apps
     from django.utils.html import escape
     
     from . import PostgreSQLTestCase
    @@ -299,6 +300,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'],
    +        )
    +
     
     @isolate_apps('postgres_tests')
     class TestChecks(PostgreSQLTestCase):
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

20

News mentions

0

No linked articles in our index yet.