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.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 1.11a1, < 1.11.23 | 1.11.23 |
DjangoPyPI | >= 2.1a1, < 2.1.11 | 2.1.11 |
DjangoPyPI | >= 2.2a1, < 2.2.4 | 2.2.4 |
Affected products
10- Django/Djangodescription
- ghsa-coords9 versionspkg:pypi/djangopkg:rpm/opensuse/python-Django&distro=openSUSE%20Leap%2015.1pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%209pkg:rpm/suse/python-Django1&distro=SUSE%20OpenStack%20Cloud%20Crowbar%209pkg:rpm/suse/python-Django&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/python-Django&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/python-Django&distro=SUSE%20Package%20Hub%2015%20SP1
>= 1.11a1, < 1.11.23+ 8 more
- (no CPE)range: >= 1.11a1, < 1.11.23
- (no CPE)range: < 2.2.4-lp151.2.3.1
- (no CPE)range: < 1.11.23-3.9.1
- (no CPE)range: < 1.11.23-3.9.1
- (no CPE)range: < 1.11.23-3.12.1
- (no CPE)range: < 1.8.19-3.15.1
- (no CPE)range: < 1.11.23-3.12.1
- (no CPE)range: < 1.11.23-3.12.1
- (no CPE)range: < 2.2.4-bp151.3.3.1
Patches
3ed682a24fca7[1.11.x] Fixed CVE-2019-14234 -- Protected JSONField/HStoreField key and index lookups against SQL injection.
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.
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.
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- lists.opensuse.org/opensuse-security-announce/2019-08/msg00025.htmlghsavendor-advisoryx_refsource_SUSEWEB
- github.com/advisories/GHSA-6r97-cj55-9hrqghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/STVX7X7IDWAH5SKE6MBMY3TEI6ZODBTK/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2019-14234ghsaADVISORY
- security.gentoo.org/glsa/202004-17ghsavendor-advisoryx_refsource_GENTOOWEB
- www.debian.org/security/2019/dsa-4498ghsavendor-advisoryx_refsource_DEBIANWEB
- docs.djangoproject.com/en/dev/releases/securityghsaWEB
- docs.djangoproject.com/en/dev/releases/security/mitrex_refsource_MISC
- github.com/django/django/commit/4f5b58f5cd3c57fee9972ab074f8dc6895d8f387ghsaWEB
- github.com/django/django/commit/ed682a24fca774818542757651bfba576c3fc3efghsaWEB
- github.com/django/django/commit/f74b3ae3628c26e1b4f8db3d13a91d52a833a975ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2019-13.yamlghsaWEB
- groups.google.com/forum/ghsaWEB
- groups.google.com/forum/mitrex_refsource_MISC
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/STVX7X7IDWAH5SKE6MBMY3TEI6ZODBTKghsaWEB
- seclists.org/bugtraq/2019/Aug/15ghsamailing-listx_refsource_BUGTRAQWEB
- security.netapp.com/advisory/ntap-20190828-0002ghsaWEB
- security.netapp.com/advisory/ntap-20190828-0002/mitrex_refsource_CONFIRM
- www.djangoproject.com/weblog/2019/aug/01/security-releasesghsaWEB
- www.djangoproject.com/weblog/2019/aug/01/security-releases/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.