10000 [5.0.x] Fixed CVE-2024-53908 -- Prevented SQL injections in direct Ha… · django/django@ff08bb6 · GitHub
[go: up one dir, main page]

Skip to content

Commit ff08bb6

Browse files
charettessarahboyce
authored andcommitted
[5.0.x] Fixed CVE-2024-53908 -- Prevented SQL injections in direct HasKeyLookup usage on Oracle.
Thanks Seokchan Yoon for the report, and Mariusz Felisiak and Sarah Boyce for the reviews.
1 parent a5a89ea commit ff08bb6

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

django/db/models/fields/json.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -216,20 +216,18 @@ def compile_json_path_final_key(self, key_transform):
216216
# Compile the final key without interpreting ints as array elements.
217217
return ".%s" % json.dumps(key_transform)
218218

219-
def as_sql(self, compiler, connection, template=None):
219+
def _as_sql_parts(self, compiler, connection):
220220
# Process JSON path from the left-hand side.
221221
if isinstance(self.lhs, KeyTransform):
222-
lhs, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(
222+
lhs_sql, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(
223223
compiler, connection
224224
)
225225
lhs_json_path = compile_json_path(lhs_key_transforms)
226226
else:
227-
lhs, lhs_params = self.process_lhs(compiler, connection)
227+
lhs_sql, lhs_params = self.process_lhs(compiler, connection)
228228
lhs_json_path = "$"
229-
sql = template % lhs
230229
# Process JSON path from the right-hand side.
231230
rhs = self.rhs
232-
rhs_params = []
233231
if not isinstance(rhs, (list, tuple)):
234232
rhs = [rhs]
235233
for key in rhs:
@@ -240,24 +238,43 @@ def as_sql(self, compiler, connection, template=None):
240238
*rhs_key_transforms, final_key = rhs_key_transforms
241239
rhs_json_path = compile_json_path(rhs_key_transforms, include_root=False)
242240
rhs_json_path += self.compile_json_path_final_key(final_key)
243-
rhs_params.append(lhs_json_path + rhs_json_path)
241+
yield lhs_sql, lhs_params, lhs_json_path + rhs_json_path
242+
243+
def _combine_sql_parts(self, parts):
244244
# Add condition for each key.
245245
if self.logical_operator:
246-
sql = "(%s)" % self.logical_operator.join([sql] * len(rhs_params))
247-
return sql, tuple(lhs_params) + tuple(rhs_params)
246+
return "(%s)" % self.logical_operator.join(parts)
247+
return "".join(parts)
248+
249+
def as_sql(self, compiler, connection, template=None):
250+
sql_parts = []
251+
params = []
252+
for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts(
253+
compiler, connection
254+
):
255+
sql_parts.append(template % (lhs_sql, "%s"))
256+
params.extend(lhs_params + [rhs_json_path])
257+
return self._combine_sql_parts(sql_parts), tuple(params)
248258

249259
def as_mysql(self, compiler, connection):
250260
return self.as_sql(
251-
compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %%s)"
261+
compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %s)"
252262
)
253263

254264
def as_oracle(self, compiler, connection):
255-
sql, params = self.as_sql(
256-
compiler, connection, template="JSON_EXISTS(%s, '%%s')"
257-
)
258-
# Add paths directly into SQL because path expressions cannot be passed
259-
# as bind variables on Oracle.
260-
return sql % tuple(params), []
265+
template = "JSON_EXISTS(%s, '%s')"
266+
sql_parts = []
267+
params = []
268+
for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts(
269+
compiler, connection
270+
):
271+
# Add right-hand-side directly into SQL because it cannot be passed
272+
# as bind variables to JSON_EXISTS. It might result in invalid
273+
# queries but it is assumed that it cannot be evaded because the
274+
# path is JSON serialized.
275+
sql_parts.append(template % (lhs_sql, rhs_json_path))
276+
params.extend(lhs_params)
277+
return self._combine_sql_parts(sql_parts), tuple(params)
261278

262279
def as_postgresql(self, compiler, connection):
263280
if isinstance(self.rhs, KeyTransform):
@@ -269,7 +286,7 @@ def as_postgresql(self, compiler, connection):
269286

270287
def as_sqlite(self, compiler, connection):
271288
return self.as_sql(
272-
compiler, connection, template="JSON_TYPE(%s, %%s) IS NOT NULL"
289+
compiler, connection, template="JSON_TYPE(%s, %s) IS NOT NULL"
273290
)
274291

275292

@@ -467,9 +484,9 @@ def as_oracle(self, compiler, connection):
467484
return "(NOT %s OR %s IS NULL)" % (sql, lhs), tuple(params) + tuple(lhs_params)
468485

469486
def as_sqlite(self, compiler, connection):
470-
template = "JSON_TYPE(%s, %%s) IS NULL"
487+
template = "JSON_TYPE(%s, %s) IS NULL"
471488
if not self.rhs:
472-
template = "JSON_TYPE(%s, %%s) IS NOT NULL"
489+
template = "JSON_TYPE(%s, %s) IS NOT NULL"
473490
return HasKeyOrArrayIndex(self.lhs.lhs, self.lhs.key_name).as_sql(
474491
compiler,
475492
connection,

docs/releases/4.2.17.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,12 @@ Remember that absolutely NO guarantee is provided about the results of
2222
``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
2323
``strip_tags()`` call without escaping it first, for example with
2424
:func:`django.utils.html.escape`.
25+
26+
CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle
27+
==========================================================================
28+
29+
Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle
30+
was subject to SQL injection if untrusted data was used as a ``lhs`` value.
31+
32+
Applications that use the :lookup:`has_key <jsonfield.has_key>` lookup through
33+
the ``__`` syntax are unaffected.

docs/releases/5.0.10.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,12 @@ Remember that absolutely NO guarantee is provided about the results of
2222
``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
2323
``strip_tags()`` call without escaping it first, for example with
2424
:func:`django.utils.html.escape`.
25+
26+
CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle
27+
==========================================================================
28+
29+
Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle
30+
was subject to SQL injection if untrusted data was used as a ``lhs`` value.
31+
32+
Applications that use the :lookup:`has_key <jsonfield.has_key>` lookup through
33+
the ``__`` syntax are unaffected.

tests/model_fields/test_jsonfield.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from django.db.models.expressions import RawSQL
3030
from django.db.models.fields.json import (
3131
KT,
32+
HasKey,
3233
KeyTextTransform,
3334
KeyTransform,
3435
KeyTransformFactory,
@@ -607,6 +608,14 @@ def test_has_key_deep(self):
607608
[expected],
608609
)
609610

611+
def test_has_key_literal_lookup(self):
612+
self.assertSequenceEqual(
613+
NullableJSONModel.objects.filter(
614+
HasKey(Value({"foo": "bar"}, JSONField()), "foo")
615+
).order_by("id"),
616+
self.objs,
617+
)
618+
610619
def test_has_key_list(self):
611620
obj = NullableJSONModel.objects.create(value=[{"a": 1}, {"b": "x"}])
612621
tests = [

0 commit comments

Comments
 (0)
0